commit a62f250df9b389acbac44925aa241dedb6b5b8f9 Author: Craig Everett Date: Thu Apr 17 01:05:03 2025 +0900 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20177b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.eunit +deps +*.o +*.beam +*.plt +*.swp +erl_crash.dump +ebin/*.beam +doc/*.html +doc/*.css +doc/edoc-info +doc/erlang.png +rel/example_project +.concrete/DEV_MODE +.rebar diff --git a/Emakefile b/Emakefile new file mode 100644 index 0000000..68c7b67 --- /dev/null +++ b/Emakefile @@ -0,0 +1 @@ +{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ebin/gajumine.app b/ebin/gajumine.app new file mode 100644 index 0000000..d968fe9 --- /dev/null +++ b/ebin/gajumine.app @@ -0,0 +1,8 @@ +{application,gajumine, + [{description,"Mining client for the Gajumaru Root"}, + {registered,[]}, + {included_applications,[]}, + {applications,[stdlib,kernel]}, + {vsn,"0.1.0"}, + {modules,[gajumine,gmc_con,gmc_gui,gmc_sup]}, + {mod,{gajumine,[]}}]}. diff --git a/src/gajumine.erl b/src/gajumine.erl new file mode 100644 index 0000000..c6fadce --- /dev/null +++ b/src/gajumine.erl @@ -0,0 +1,38 @@ +%%% @doc +%%% GajuMine +%%% @end + +-module(gajumine). +-vsn("0.1.0"). +-behavior(application). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-export([start/2, stop/1]). + +-include("$zx_include/zx_logger.hrl"). + +-spec start(normal, Args :: term()) -> {ok, pid()}. + +start(normal, _Args) -> + ok = + case net_kernel:stop() of + ok -> + log(info, "SAFE: This node is running standalone."); + {error, not_found} -> + log(warning, "SAFETY: Distribution has been disabled on this node."); + {error, not_allowed} -> + ok = tell(error, "DANGER! This node is in distributed mode!"), + init:stop(1) + end, + ok = application:ensure_started(hakuzaru), + ok = application:ensure_started(zxwidgets), + ok = application:ensure_started(sophia), + gmc_sup:start_link(). + + +-spec stop(term()) -> ok. + +stop(_State) -> + ok. diff --git a/src/gmc_con.erl b/src/gmc_con.erl new file mode 100644 index 0000000..1a2ec3c --- /dev/null +++ b/src/gmc_con.erl @@ -0,0 +1,332 @@ +%%% @doc +%%% GajuMine Controller +%%% @end + +-module(gmc_con). +-vsn("0.1.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-behavior(gen_server). +-export([unlock/1, make_key/1, load_key/2, end_setup/0]). +-export([network/0]). +-export([start_link/0, stop/0]). +-export([init/1, terminate/2, code_change/3, + handle_call/3, handle_cast/2, handle_info/2]). +-include("$zx_include/zx_logger.hrl"). +-include("$gajudesk_include/gd.hrl"). + + +%%% Type and Record Definitions + + +-record(s, + {version = 1 :: integer(), + window = none :: none | wx:wx_object(), + network = none :: none | mainnet | testnet, + key = none :: none | {blob, binary()} | #key{}, + pass = none :: none | binary()}). + +-type state() :: #s{}. + + + +%% Interface + +-spec unlock(Phrase) -> ok + when Phrase :: string(). + +unlock(Phrase) -> + gen_server:cast(?MODULE, {unlock, Phrase}). + + +-spec make_key(Phrase) -> ok + when Phrase :: string(). + +make_key(Phrase) -> + gen_server:cast(?MODULE, {make_key, Phrase}). + + +-spec load_key(Phrase, Mnemonic) -> ok + when Phrase :: string(), + Mnemonic :: string(). + +load_key(Phrase, Mnemonic) -> + gen_server:cast(?MODULE, {load_key, Phrase, Mnemonic}). + + +-spec end_setup() -> ok. + +end_setup() -> + gen_server:call(?MODULE, end_setup). + + +-spec network() -> mainnet | testnet | none. + +network() -> + gen_server:call(?MODULE, network). + + +-spec stop() -> ok. + +stop() -> + gen_server:cast(?MODULE, stop). + + + +%%% Startup Functions + + +-spec start_link() -> Result + when Result :: {ok, pid()} + | {error, Reason}, + Reason :: {already_started, pid()} + | {shutdown, term()} + | term(). +%% @private +%% Called by gmc_sup. + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, none, []). + + +-spec init(none) -> {ok, state()}. + +init(none) -> + ok = log(info, "Starting"), + _ = process_flag(sensitive, true), + case open_wallet() of + {blob, Binary} -> + NewState = start_gui(#s{key = {blob, Binary}}), + ok = gmc_gui:ask_passphrase(), + {ok, NewState}; + {ok, Wallet} -> + NewState = start_gui(#s{key = Wallet}), + {ok, NewState}; + error -> + Window = gmc_setup:start_link(#{}), + ok = log(info, "Window: ~p", [Window]), + State = #s{window = Window}, + {ok, State} + end. + +open_wallet() -> + GDPrefs = gd_read_prefs(), + Wallets = gd_get_prefs(wallets, GDPrefs, []), + case lists:keyfind(gm_name(), #wr.name, Wallets) of + #wr{path = Path, pass = true} -> + case file:read_file(Path) of + {ok, Binary} -> {blob, Binary}; + Error -> Error + end; + #wr{path = Path, pass = false} -> + case file:read_file(Path) of + {ok, Binary} -> + % TODO: Comply with GD's wallet upgrade system + {ok, #wallet{keys = Keys}} = zx_lib:b_to_t(Binary), + Key = lists:keyfind(gm_name(), #key.name, Keys), + {ok, Key}; + Error -> + Error + end; + false -> + error + end. + + +start_gui(State = #s{key = #key{id = ID}}) -> + Window = gmc_gui:start_link(#{}), + ok = gmc_gui:set_account(ID), + State#s{window = Window}; +start_gui(State) -> + Window = gmc_gui:start_link(#{}), + State#s{window = Window}. + + +%%% gen_server Message Handling Callbacks + + +handle_call(end_setup, _, State) -> + NewState = end_setup(State), + {reply, ok, NewState}; +handle_call(network, _, State = #s{network = Network}) -> + {reply, Network, State}; +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), + {noreply, State}. + + +handle_cast({unlock, Phrase}, State) -> + NewState = unlock(Phrase, State), + {noreply, NewState}; +handle_cast({make_key, Phrase}, State) -> + NewState = make_key(Phrase, State), + {noreply, NewState}; +handle_cast({load_key, Phrase, Mnemonic}, State) -> + NewState = load_key(Phrase, Mnemonic, State), + {noreply, NewState}; +handle_cast(stop, State) -> + ok = log(info, "Received a 'stop' message."), + {stop, normal, State}; +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), + {noreply, State}. + + +code_change(_, State, _) -> + {ok, State}. + + +terminate(Reason, _) -> + ok = log(info, "Reason: ~p,", [Reason]), + case whereis(gd_con) of + undefined -> + zx:stop(); + PID -> + ok = log(info, "gd_con found at: ~p", [PID]), + application:stop(gajumine) + end. + + +%%% Doers + +unlock(Phrase, State = #s{key = {blob, Cipher}}) -> + Pass = pass(unicode:characters_to_binary(Phrase)), + case decrypt(Pass, Cipher) of + {ok, Binary} -> + {ok, #wallet{keys = Keys}} = zx_lib:b_to_t(Binary), + Name = gm_name(), + Unlocked = #key{id = ID, pair = Pair} = lists:keyfind(Name, #key.name, Keys), + #{secret := Secret} = Pair, + Encrypted = Pair#{secret => encrypt(Pass, Secret)}, + ok = gmc_gui:set_account(ID), + State#s{key = Unlocked#key{pair = Encrypted}, pass = Pass}; + {error, bad_password} -> + ok = gmc_gui:ask_passphrase(), + State + end. + + +make_key(Phrase, State) -> + {ID, Pair = #{secret := <>}} = hz_key_master:make_key(<<>>), + Mnemonic = hz_key_master:encode(K), + ok = gmc_setup:done(Mnemonic), + finalize_key(Phrase, ID, Pair, State). + + +load_key(Phrase, Mnemonic, State) -> + case hz_key_master:decode(Mnemonic) of + {ok, Secret} -> + {ID, Pair} = hz_key_master:make_key(Secret), + finalize_key(Phrase, ID, Pair, State); + Error -> + ok = gmc_gui:trouble(Error), + ok = gmc_gui:bad_mnemonic(), + State + end. + + +finalize_key(Phrase, ID, Pair, State) -> + Pass = pass(unicode:characters_to_binary(Phrase)), + Key = #key{id = ID} = add_wallet(Pass, ID, Pair), + State#s{key = Key, pass = Pass}. + + +add_wallet(Pass, ID, Pair = #{secret := Secret}) -> + GDPrefsPath = gd_prefs_path(), + GDPrefs = + case file:consult(GDPrefsPath) of + {ok, Prefs} -> proplists:to_map(Prefs); + {error, enoent} -> #{} + end, + Wallets = gd_get_prefs(wallets, GDPrefs, []), + WalletPath = gajumining_wallet(), + Entry = #wr{name = gm_name(), path = WalletPath, pass = true}, + NewWallets = lists:keystore(gm_name(), #wr.name, Wallets, Entry), + NewGDPrefs = gd_put_prefs(wallets, NewWallets, GDPrefs), + Created = #key{name = gm_name(), id = ID, pair = Pair}, + New = #wallet{name = gm_name(), + poas = [#poa{name = gm_name(), id = ID}], + keys = [Created], + chain_id = <<"groot.devnet">>, + endpoint = #node{}, + nets = [#net{}]}, + Cipher = encrypt(Pass, term_to_binary(New)), + ok = file:write_file(WalletPath, Cipher), + ok = zx_lib:write_terms(GDPrefsPath, proplists:from_map(NewGDPrefs)), + Encrypted = Pair#{secret => encrypt(Pass, Secret)}, + Created#key{pair = Encrypted}. + + +end_setup(State = #s{window = Window}) -> + PID = wx_object:get_pid(Window), + true = unlink(PID), + start_gui(State#s{window = none}). + + + +%%% Utils + +%% Encryption stuff +% TODO: Expose these from GD itself + +encrypt(Pass, Binary) -> + Flags = [{encrypt, true}, {padding, pkcs_padding}], + crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags). + + +decrypt(Pass, Binary) -> + Flags = [{encrypt, false}, {padding, pkcs_padding}], + try + {ok, crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags)} + catch + error:{error, L, "Can't finalize"} -> + ok = log(info, "Decrypt failed at ~p", [L]), + {error, bad_password}; + E:R -> + {E, R} + end. + + +pass(Phrase) -> + crypto:hash(sha3_256, Phrase). + + +%% Paths and Names + +gm_name() -> + "GajuMine". + +gajumining_wallet() -> + filename:join(zx_lib:path(var, "otpr", "gajudesk"), "gajumining.gaju"). + + +gd_prefs_path() -> + filename:join(zx_lib:path(etc, "otpr", "gajudesk"), "prefs.eterms"). + + +%% GajuDesk Prefs +% TODO: Glob these into a gd_prefs module exposed from GD itself + +gd_get_prefs(K, M, D) -> + P = maps:get(gd_con, M, #{}), + maps:get(K, P, D). + + +gd_put_prefs(K, V, M) -> + P = maps:get(gd_con, M, #{}), + NewP = maps:put(K, V, P), + maps:put(gd_con, NewP, M). + + +gd_read_prefs() -> + case file:consult(gd_prefs_path()) of + {ok, Prefs} -> proplists:to_map(Prefs); + {error, enoent} -> #{} + end. diff --git a/src/gmc_gui.erl b/src/gmc_gui.erl new file mode 100644 index 0000000..be77306 --- /dev/null +++ b/src/gmc_gui.erl @@ -0,0 +1,292 @@ +%%% @doc +%%% GajuMine GUI +%%% @end + +-module(gmc_gui). +-vsn("0.1.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-behavior(wx_object). +-include_lib("wx/include/wx.hrl"). +-export([ask_passphrase/0, set_account/1, show/1]). +-export([start_link/1]). +-export([init/1, terminate/2, code_change/3, + handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). +-include("$zx_include/zx_logger.hrl"). + + +-record(w, + {name = none :: atom(), + id = 0 :: integer(), + wx = none :: none | wx:wx_object()}). + +-record(s, + {wx = none :: none | wx:wx_object(), + frame = none :: none | wx:wx_object(), + lang = en :: en | jp, + j = none :: none | fun(), + id = none :: none | wx:wx_object(), + diff = none :: none | wx:wx_object(), + perf = none :: none | wx:wx_object(), + candy = none :: none | wx:wx_object(), + mess = none :: none | wx:wx_object(), + buff = [] :: [string()], + buttons = [] :: [#w{}]}). + + + +%%% Interface functions + +set_account(ID) -> + wx_object:cast(?MODULE, {set_account, ID}). + + +ask_passphrase() -> + wx_object:cast(?MODULE, ask_passphrase). + + +show(Terms) -> + wx_object:cast(?MODULE, {show, Terms}). + + + +%%% Startup Functions + +start_link(Title) -> + wx_object:start_link({local, ?MODULE}, ?MODULE, Title, []). + + +init(Prefs) -> + ok = log(info, "GUI starting..."), + Lang = maps:get(lang, Prefs, en_US), + Trans = gd_jt:read_translations(?MODULE), + J = gd_jt:j(Lang, Trans), + + WX = wx:new(), + Frame = wxFrame:new(WX, ?wxID_ANY, J("GajuMine")), + MainSz = wxBoxSizer:new(?wxVERTICAL), + LeftRight = wxBoxSizer:new(?wxHORIZONTAL), + Left = wxBoxSizer:new(?wxVERTICAL), + Right = wxBoxSizer:new(?wxVERTICAL), + + Labels = [J("ID"), J("Difficulty"), J("Maps/s"), J("Candidate")], + {Grid, [ID_C, DiffC, PerfC, CandyC]} = display_box(Frame, Labels), + + Style = ?wxDEFAULT bor ?wxTE_MULTILINE bor ?wxTE_READONLY, + MessSz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Messages")}]), + MessC = wxTextCtrl:new(Frame, ?wxID_ANY, [{style, Style}]), + _ = wxStaticBoxSizer:add(MessSz, MessC, zxw:flags(wide)), + + ButtonTemplates = + [{start_stop, J("Start")}, + {gajudesk, J("Open Wallet")}, + {eureka, J("GajuMining")}, + {explorer, J("ChainExplorer")}], + + MakeButton = + fun({Name, Label}) -> + B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + #w{name = Name, id = wxButton:getId(B), wx = B} + end, + + Buttons = lists:map(MakeButton, ButtonTemplates), + + _ = wxBoxSizer:add(Left, Grid, zxw:flags(base)), + _ = wxBoxSizer:add(Left, MessSz, zxw:flags(wide)), + Add = fun(#w{wx = Button}) -> wxBoxSizer:add(Right, Button, zxw:flags(wide)) end, + ok = lists:foreach(Add, Buttons), + _ = wxBoxSizer:add(LeftRight, Left, zxw:flags(wide)), + _ = wxBoxSizer:add(LeftRight, Right, zxw:flags(base)), + _ = wxBoxSizer:add(MainSz, LeftRight, zxw:flags(wide)), + + ok = wxFrame:setSizer(Frame, MainSz), + ok = wxSizer:layout(MainSz), + + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + ok = wxFrame:setSize(Frame, {650, 160}), + ok = wxFrame:center(Frame), + true = wxFrame:show(Frame), + State = + #s{wx = WX, frame = Frame, + lang = Lang, j = J, + id = ID_C, diff = DiffC, perf = PerfC, candy = CandyC, + mess = MessC, + buttons = Buttons}, + {Frame, State}. + +display_box(Parent, Labels) -> + Grid = wxFlexGridSizer:new(2, 4, 4), + ok = wxFlexGridSizer:setFlexibleDirection(Grid, ?wxHORIZONTAL), + ok = wxFlexGridSizer:addGrowableCol(Grid, 1), + Make = + fun(S) -> + L = [S, ":"], + T_L = wxStaticText:new(Parent, ?wxID_ANY, L), + T_C = wxStaticText:new(Parent, ?wxID_ANY, "", [{style, ?wxALIGN_RIGHT}]), + _ = wxFlexGridSizer:add(Grid, T_L, zxw:flags(base)), + _ = wxFlexGridSizer:add(Grid, T_C, zxw:flags(wide)), + T_C + end, + Controls = lists:map(Make, Labels), + {Grid, Controls}. + + +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), + {noreply, State}. + + +handle_cast(ask_passphrase, State) -> + ok = ask_passphrase(State), + {noreply, State}; +handle_cast({set_account, ID}, State) -> + ok = set_account(ID, State), + {noreply, State}; +handle_cast({show, Terms}, State) -> + ok = do_show(Terms, State), + {noreply, State}; +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, + State = #s{buttons = Buttons}) -> + NewState = + case lists:keyfind(ID, #w.id, Buttons) of + #w{name = start_stop} -> start_stop(State); + #w{name = gajudesk} -> gajudesk(State); + #w{name = eureka} -> eureka(State); + #w{name = explorer} -> explorer(State); + false -> State + end, + {noreply, NewState}; +handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame}) -> + ok = retire(Frame), + {noreply, State}; +handle_event(Event, State) -> + ok = tell(info, "Unexpected event ~tp", [Event]), + {noreply, State}. + + +code_change(_, State, _) -> + {ok, State}. + + +terminate(Reason, State) -> + ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), + wx:destroy(). + + +ask_passphrase(#s{frame = Frame, j = J}) -> + Label = J("Unlock Account"), + Dialog = wxDialog:new(Frame, ?wxID_ANY, Label, [{size, {500, 115}}]), + Sizer = wxBoxSizer:new(?wxVERTICAL), + PassSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Password")}]), + PassTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxStaticBoxSizer:add(PassSz, PassTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, PassSz, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Phrase = wxTextCtrl:getValue(PassTx), + gmc_con:unlock(Phrase); + ?wxID_CANCEL -> + retire(Frame) + end, + wxDialog:destroy(Dialog). + + +set_account(ID, #s{id = Widget}) -> + wxStaticText:setLabel(Widget, ID). + + +do_show(Terms, #s{mess = Mess}) -> + String = io_lib:format("Received args: ~tp", [Terms]), + wxTextCtrl:changeValue(Mess, String). + + +start_stop(State) -> + ok = gmc_con:start_stop(), + State. + + +gajudesk(State) -> + ok = tell(info, "Running gajudesk"), + Launch = + fun() -> + R = "otpr", + N = "gajudesk", + {ok, V} = zx:latest({R, N}), + {ok, PackageString} = zx_lib:package_string({R, N, V}), + try + case zx:run(PackageString, []) of + ok -> ok; + Error -> tell(error, "gajudesk died with: ~p", [Error]) + end + catch + E:R -> tell(error, "gajudesk died with: ~p", [{E, R}]) + end + end, + PID = spawn(Launch), + ok = tell(info, "GajuDesk launched at PID: ~p", [PID]), + State. + + +eureka(State = #s{frame = Frame, j = J}) -> + ok = tell(info, "Opening Eureka"), + ok = open_browser(Frame, J, eureka_url()), + State. + +eureka_url() -> + case gmc_con:network() of + mainnet -> "https://eureka.gajumining.com"; + testnet -> "https://eureka.test.gajumarumining.com"; + none -> "https://gajumarumining.com" + end. + + +explorer(State = #s{frame = Frame, j = J}) -> + ok = tell(info, "Opening Explorer"), + ok = open_browser(Frame, J, explorer_url()), + State. + +explorer_url() -> + case gmc_con:network() of + mainnet -> "https://groot.mainnet.gajumaru.io"; + testnet -> "https://groot.testnet.gajumaru.io"; + none -> "https://gajumaru.io" + end. + + +open_browser(Frame, J, URL) -> + case wx_misc:launchDefaultBrowser(URL) of + true -> + ok; + false -> + Format = J("Trouble launching browser.\nOpen URL here: ~s"), + Message = io_lib:format(Format, [URL]), + zxw:show_message(Frame, Message) + end. + + +retire(Frame) -> + ok = gmc_con:stop(), + wxWindow:destroy(Frame). diff --git a/src/gmc_setup.erl b/src/gmc_setup.erl new file mode 100644 index 0000000..47f480a --- /dev/null +++ b/src/gmc_setup.erl @@ -0,0 +1,298 @@ +%%% @doc +%%% GajuMine Setup GUI +%%% +%%% In the common case, when a user first runs the program they will not have any +%%% accounrts, keys, or mining system setup. The purpose of this program is to provide +%%% a first-run guide for them to get set up for mining and explain to the user what is +%%% going on. +%%% +%%% There are two cases to cover: +%%% 1. A new miner who doesn't know how anything works and needs to get set up from scratch. +%%% 2. A miner who does know a bit about how things work and wants to import a key. +%%% @end + +-module(gmc_setup). +-vsn("0.1.0"). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-behavior(wx_object). +-include_lib("wx/include/wx.hrl"). +-export([trouble/1, done/1]). +-export([start_link/1]). +-export([init/1, terminate/2, code_change/3, + handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). +-include("$zx_include/zx_logger.hrl"). + + +-record(w, + {name = none :: atom(), + id = 0 :: integer(), + wx = none :: none | wx:wx_object()}). + +-record(s, + {wx = none :: none | wx:wx_object(), + frame = none :: none | wx:wx_object(), + lang = en :: en | jp, + j = none :: none | fun(), + buttons = [] :: [#w{}]}). + + + +%%% Interface functions + +trouble(Info) -> + wx_object:cast(?MODULE, {trouble, Info}). + + +done(Mnemonic) -> + wx_object:cast(?MODULE, {done, Mnemonic}). + + + +%%% Startup Functions + +start_link(Prefs) -> + wx_object:start_link({local, ?MODULE}, ?MODULE, Prefs, []). + + +init(Prefs) -> + ok = log(info, "GUI starting..."), + Lang = maps:get(lang, Prefs, en_US), + Trans = gd_jt:read_translations(?MODULE), + J = gd_jt:j(Lang, Trans), + + WX = wx:new(), + Frame = wxFrame:new(WX, ?wxID_ANY, J("GajuMine Setup")), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + ButtonTemplates = + [{new_key, J("Create a New Account")}, + {recover, J("Recover Existing Account")}], + + MakeButton = + fun({Name, Label}) -> + B = wxButton:new(Frame, ?wxID_ANY, [{label, Label}]), + #w{name = Name, id = wxButton:getId(B), wx = B} + end, + + Buttons = lists:map(MakeButton, ButtonTemplates), + + Add = fun(#w{wx = Button}) -> wxBoxSizer:add(MainSz, Button, zxw:flags(wide)) end, + ok = lists:foreach(Add, Buttons), + + ok = wxFrame:setSizer(Frame, MainSz), + ok = wxSizer:layout(MainSz), + + ok = wxFrame:connect(Frame, command_button_clicked), + ok = wxFrame:connect(Frame, close_window), + ok = wxFrame:setSize(Frame, {300, 300}), + ok = wxFrame:center(Frame), + true = wxFrame:show(Frame), + State = + #s{wx = WX, frame = Frame, + lang = Lang, j = J, + buttons = Buttons}, + {Frame, State}. + + +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), + {noreply, State}. + + +handle_cast({trouble, Info}, State) -> + ok = handle_troubling(State, Info), + {noreply, State}; +handle_cast({done, Mnemonic}, State) -> + ok = done(Mnemonic, State), + {noreply, State}; +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, + id = ID}, + State = #s{buttons = Buttons}) -> + ok = + case lists:keyfind(ID, #w.id, Buttons) of + #w{name = new_key} -> new_key(State); + #w{name = recover} -> recover(State) + end, + {noreply, State}; +handle_event(#wx{event = #wxClose{}}, State = #s{frame = Frame}) -> + ok = gmc_con:stop(), + ok = wxWindow:destroy(Frame), + {noreply, State}; +handle_event(Event, State) -> + ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), + {noreply, State}. + + +handle_troubling(#s{frame = Frame}, Info) -> + zxw:show_message(Frame, Info). + + +code_change(_, State, _) -> + {ok, State}. + + +terminate(Reason, State) -> + ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), + wx:destroy(). + + +new_key(State = #s{frame = Frame, j = J}) -> + case prompt_passphrase(Frame, J) of + {ok, Phrase} -> + gmc_con:make_key(Phrase); + mismatch -> + ok = zxw:show_message(Frame, J("Password entered incorrectly. Try again.")), + new_key(State); + cancel -> + ok + end. + + +prompt_passphrase(Frame, J) -> + Label = J("Generate New Account Key"), + Dialog = wxDialog:new(Frame, ?wxID_ANY, Label, [{size, {500, 180}}]), + Sizer = wxBoxSizer:new(?wxVERTICAL), + PassSz1 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Create Password")}]), + PassTx1 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxStaticBoxSizer:add(PassSz1, PassTx1, zxw:flags(wide)), + PassSz2 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Confirm Password")}]), + PassTx2 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxStaticBoxSizer:add(PassSz2, PassTx2, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, PassSz1, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, PassSz2, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(wide)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + Outcome = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Pass1 = wxTextCtrl:getValue(PassTx1), + Pass2 = wxTextCtrl:getValue(PassTx2), + case Pass1 =:= Pass2 of + true -> {ok, Pass1}; + false -> mismatch + end; + ?wxID_CANCEL -> + cancel + end, + ok = wxDialog:destroy(Dialog), + Outcome. + + +recover(State = #s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Import Account Key")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + PassSz1 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Create Password")}]), + PassTx1 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxStaticBoxSizer:add(PassSz1, PassTx1, zxw:flags(wide)), + PassSz2 = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Confirm Password")}]), + PassTx2 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_PASSWORD}]), + _ = wxStaticBoxSizer:add(PassSz2, PassTx2, zxw:flags(wide)), + MnemSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Recovery Phrase")}]), + MnemTx = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_MULTILINE}]), + _ = wxStaticBoxSizer:add(MnemSz, MnemTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + Affirm = wxButton:new(Dialog, ?wxID_OK), + Cancel = wxButton:new(Dialog, ?wxID_CANCEL), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, PassSz1, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, PassSz2, zxw:flags(base)), + _ = wxBoxSizer:add(Sizer, MnemSz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + Outcome = + case wxDialog:showModal(Dialog) of + ?wxID_OK -> + Pass1 = wxTextCtrl:getValue(PassTx1), + Pass2 = wxTextCtrl:getValue(PassTx2), + case Pass1 =:= Pass2 of + true -> + Mnemonic = wxTextCtrl:getValue(MnemTx), + gmc_con:load_key(Pass1, Mnemonic); + false -> + mismatch + end; + ?wxID_CANCEL -> + cancel + end, + ok = wxDialog:destroy(Dialog), + case Outcome of + ok -> + ok; + mismatch -> + ok = zxw:show_message(Frame, J("Password entered incorrectly. Try again.")), + recover(State); + cancel -> + ok + end. + + + +done(Mnemonic, #s{frame = Frame, j = J}) -> + Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Mnemonic")), + Sizer = wxBoxSizer:new(?wxVERTICAL), + MnemSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Recovery Phrase")}]), + Options = [{value, Mnemonic}, {style, ?wxTE_MULTILINE bor ?wxTE_READONLY}], + MnemTx = wxTextCtrl:new(Dialog, ?wxID_ANY, Options), + _ = wxStaticBoxSizer:add(MnemSz, MnemTx, zxw:flags(wide)), + ButtSz = wxBoxSizer:new(?wxHORIZONTAL), + CloseB = wxButton:new(Dialog, ?wxID_CANCEL, [{label, J("Close")}]), + CopyB = wxButton:new(Dialog, ?wxID_OK, [{label, J("Copy to Clipboard")}]), + _ = wxBoxSizer:add(ButtSz, CloseB, zxw:flags(wide)), + _ = wxBoxSizer:add(ButtSz, CopyB, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, MnemSz, zxw:flags(wide)), + _ = wxBoxSizer:add(Sizer, ButtSz, zxw:flags(base)), + ok = wxDialog:setSizer(Dialog, Sizer), + ok = wxBoxSizer:layout(Sizer), + ok = wxFrame:center(Dialog), + ok = wxStyledTextCtrl:setFocus(MnemTx), + ok = + case wxDialog:showModal(Dialog) of + ?wxID_CANCEL -> ok; + ?wxID_OK -> copy_to_clipboard(Mnemonic) + end, + ok = wxDialog:destroy(Dialog), + ok = gmc_con:end_setup(), + ok = wxWindow:destroy(Frame), + ok. + + +copy_to_clipboard(String) -> + CB = wxClipboard:get(), + case wxClipboard:open(CB) of + true -> + Text = wxTextDataObject:new([{text, String}]), + case wxClipboard:setData(CB, Text) of + true -> + R = wxClipboard:flush(CB), + log(info, "String copied to system clipboard. Flushed: ~p", [R]); + false -> + log(info, "Failed to copy to clipboard") + end, + ok = wxClipboard:close(CB); + false -> + log(info, "Failed to acquire the clipboard.") + end. + diff --git a/src/gmc_sup.erl b/src/gmc_sup.erl new file mode 100644 index 0000000..c61a56c --- /dev/null +++ b/src/gmc_sup.erl @@ -0,0 +1,46 @@ +%%% @doc +%%% GajuMine Top-level Supervisor +%%% +%%% The very top level supervisor in the system. It only has one service branch: the +%%% "con" (program controller). The con is the +%%% only be one part of a larger system. Were this a game system, for example, the +%%% item data management service would be a peer, as would a login credential provision +%%% service, game world event handling, and so on. +%%% +%%% See: http://erlang.org/doc/design_principles/applications.html +%%% See: http://zxq9.com/archives/1311 +%%% @end + +-module(gmc_sup). +-vsn("0.1.0"). +-behaviour(supervisor). +-author("Craig Everett "). +-copyright("Craig Everett "). +-license("GPL-3.0-or-later"). + +-export([start_link/0]). +-export([init/1]). + + +-spec start_link() -> {ok, pid()}. +%% @private +%% This supervisor's own start function. + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + + +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +%% @private +%% The OTP init/1 function. + +init([]) -> + RestartStrategy = {one_for_one, 0, 60}, + Clients = {gmc_con, + {gmc_con, start_link, []}, + permanent, + 5000, + worker, + [gmc_con]}, + Children = [Clients], + {ok, {RestartStrategy, Children}}. diff --git a/zomp.meta b/zomp.meta new file mode 100644 index 0000000..eba1023 --- /dev/null +++ b/zomp.meta @@ -0,0 +1,27 @@ +{name,"GajuMine"}. +{type,gui}. +{modules,[]}. +{prefix,"gmc"}. +{author,"Craig Everett"}. +{desc,"Mining client for the Gajumaru Root"}. +{package_id,{"qpq","gajumine",{0,1,0}}}. +{deps,[{"otpr","gajudesk",{0,5,3}}, + {"otpr","zxwidgets",{1,0,1}}, + {"otpr","ec_utils",{1,0,0}}, + {"otpr","eblake2",{1,0,1}}, + {"otpr","base58",{0,1,1}}, + {"otpr","zj",{1,1,0}}, + {"otpr","lom",{1,0,0}}, + {"otpr","gmbytecode",{3,4,1}}, + {"otpr","sophia",{9,0,0}}, + {"otpr","gmserialization",{0,1,3}}, + {"otpr","hakuzaru",{0,6,0}}]}. +{key_name,none}. +{a_email,"craigeverett@qpq.swiss"}. +{c_email,"craigeverett@qpq.swiss"}. +{copyright,"Craig Everett"}. +{file_exts,[]}. +{license,"GPL-3.0-or-later"}. +{repo_url,"https://git.qpq.swiss/zxq9/GajuMine"}. +{tags,[]}. +{ws_url,"https://gajumining.com"}.