server

Clone or download

Initial Commit

Modified Files

--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+patreon: streampi
A .gitignore
+11 −0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+target/
+.idea/
+data/
+streampi.log
+streampi.log.lck
+Server.iml
+
+.settings/
+.project
+.factorypath
+.classpath
Binary files /dev/null and b/BakPlugins/HotkeyAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-Mother-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetCurrentProfileAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetCurrentSceneAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetCurrentTransition-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetMuteAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetPreviewSceneAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetReplayBufferAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetStreamingAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetStudioModeAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/OBSSuite-SetVolumeAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/PlayAudioClipAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/TwitterAction-1.0.0.jar differ
Binary files /dev/null and b/BakPlugins/WebsiteAction-1.0.0.jar differ
--- /dev/null
+++ b/BakPlugins/config.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><configuration>
+ <actions><action><module-name>com.StreamPi.PlayAudioClipAction</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetCurrentScene</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.Mother</module-name><version>1.0.0</version><properties><property><name>url</name><value>ws://localhost:4444</value></property><property><name>pass</name><value/></property><property><name>connect_on_startup</name><value>false</value></property></properties></action><action><module-name>com.StreamPi.WebsiteAction</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetCurrentTransition</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetStreaming</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetCurrentProfile</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetReplayBuffer</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetVolume</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetPreviewScene</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.TwitterAction</module-name><version>1.0.0</version><properties><property><name>consumer_key</name><value>nothing</value></property><property><name>consumer_key_secret</name><value>nothing</value></property><property><name>access_token</name><value/></property><property><name>access_token_secret</name><value/></property></properties></action><action><module-name>com.StreamPi.OBSSuite.SetMute</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.HotkeyAction</module-name><version>1.0.0</version><properties/></action><action><module-name>com.StreamPi.OBSSuite.SetStudioMode</module-name><version>1.0.0</version><properties/></action></actions>
+</configuration>
\ No newline at end of file
Binary files /dev/null and b/BakPlugins/gson-2.8.6.jar differ
Binary files /dev/null and b/BakPlugins/jetty-client-9.4.35.v20201120.jar differ
Binary files /dev/null and b/BakPlugins/jetty-http-9.4.35.v20201120.jar differ
Binary files /dev/null and b/BakPlugins/jetty-io-9.4.35.v20201120.jar differ
Binary files /dev/null and b/BakPlugins/jetty-util-9.4.35.v20201120.jar differ
Binary files /dev/null and b/BakPlugins/obs-websocket-java-1.0.5.jar differ
Binary files /dev/null and b/BakPlugins/twitter4j-core-4.0.7.jar differ
Binary files /dev/null and b/BakPlugins/websocket-api-9.4.35.v20201120.jar differ
Binary files /dev/null and b/BakPlugins/websocket-client-9.4.35.v20201120.jar differ
Binary files /dev/null and b/BakPlugins/websocket-common-9.4.35.v20201120.jar differ
A LICENSE
+674 −0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <https://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<https://www.gnu.org/licenses/>.
+
+ 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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
\ No newline at end of file
A pom.xml
+170 −0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.StreamPi</groupId>
+ <artifactId>Server</artifactId>
+ <version>1.0.0</version>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>11</maven.compiler.source>
+ <maven.compiler.target>11</maven.compiler.target>
+
+
+ <client.plugin.version>0.1.35</client.plugin.version>
+
+ <JavaFXVersion>16-ea+5</JavaFXVersion>
+
+
+ <CommonsBeanUtilsVersion>1.9.4</CommonsBeanUtilsVersion>
+ <CommonsConfigurationVersion>2.7</CommonsConfigurationVersion>
+
+ <ActionAPIVersion>1.0.0</ActionAPIVersion>
+ <ThemeAPIVersion>1.0.0</ThemeAPIVersion>
+ <UtilVersion>1.0.0</UtilVersion>
+ <!--CommAPIVersion>1.0.0</CommAPIVersion-->
+
+ <Log4JVersion>2.14.0</Log4JVersion>
+
+ <IkonliVersion>11.5.0</IkonliVersion>
+ <IkonliFA5PackVersion>11.5.0</IkonliFA5PackVersion>
+
+
+ <MainClassName>com.StreamPi.Server.Main</MainClassName>
+ </properties>
+
+
+ <dependencies>
+ <dependency>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-controls</artifactId>
+ <version>${JavaFXVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-media</artifactId>
+ <version>${JavaFXVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.kordamp.ikonli</groupId>
+ <artifactId>ikonli-fontawesome5-pack</artifactId>
+ <version>${IkonliFA5PackVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.kordamp.ikonli</groupId>
+ <artifactId>ikonli-javafx</artifactId>
+ <version>${IkonliVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-base</artifactId>
+ <version>${JavaFXVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.StreamPi</groupId>
+ <artifactId>ActionAPI</artifactId>
+ <version>${ActionAPIVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.StreamPi</groupId>
+ <artifactId>Util</artifactId>
+ <version>${UtilVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.StreamPi</groupId>
+ <artifactId>ThemeAPI</artifactId>
+ <version>${ThemeAPIVersion}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ <version>20201115</version>
+ </dependency>
+ </dependencies>
+
+
+ <repositories>
+ <repository>
+ <id>gluon-releases</id>
+ <url>http://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
+ </repository>
+ </repositories>
+
+ <build>
+
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <release>11</release>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.openjfx</groupId>
+ <artifactId>javafx-maven-plugin</artifactId>
+ <version>0.0.5</version>
+ <configuration>
+ <commandlineArgs>-DStreamPi.startupRunnerFileName=run_min -DStreamPi.startupMode=maximise</commandlineArgs>
+ <options>
+ <option>-Dglass.gtk.uiScale=1.0</option>
+ </options>
+ <mainClass>${MainClassName}</mainClass>
+ </configuration>
+ </plugin>
+
+
+ <plugin>
+ <groupId>com.gluonhq</groupId>
+ <artifactId>client-maven-plugin</artifactId>
+ <version>${client.plugin.version}</version>
+ <configuration>
+ <bundlesList>
+ <list>com.sun.org.apache.xerces.internal.impl.msg.XMLMessages</list>
+ </bundlesList>
+ <reflectionList>
+ <list>java.util.logging.FileHandler</list>
+ </reflectionList>
+ <nativeImageArgs>--report-unsupported-elements-at-runtime,--initialize-at-build-time=com.sun.org.apache.xml.internal.serializer.ToXMLStream,-Dsvm.targetname=android,--enable-https,--enable-http,-H:Log=registerResource</nativeImageArgs>
+ <mainClass>${MainClassName}</mainClass>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.2.0</version>
+ <configuration>
+ <includeEmptyDirs>true</includeEmptyDirs>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+
+
+ <pluginRepositories>
+ <pluginRepository>
+ <id>gluon-releases</id>
+ <url>http://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
+ </pluginRepository>
+ <pluginRepository>
+ <id>gluon-snapshots</id>
+ <url>https://nexus.gluonhq.com/nexus/content/repositories/public-snapshots</url>
+ </pluginRepository>
+ </pluginRepositories>
+
+</project>
\ No newline at end of file
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Action/NormalActionPlugins.java
@@ -0,0 +1,503 @@
+package com.StreamPi.Server.Action;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.ServerConnection;
+import com.StreamPi.ActionAPI.Action.PropertySaver;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.ActionProperty.Property.ControlType;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.ActionAPI.ActionProperty.ServerProperties;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.Server.Controller.Controller;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+import com.StreamPi.Util.Version.Version;
+import com.StreamPi.Util.XMLConfigHelper.XMLConfigHelper;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import javafx.scene.image.ImageView;
+import org.w3c.dom.NodeList;
+import org.kordamp.ikonli.javafx.FontIcon;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import java.io.File;
+import java.lang.module.*;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import org.w3c.dom.Element;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+
+public class NormalActionPlugins
+{
+ private static NormalActionPlugins instance = null;
+ private final Logger logger;
+
+ private File configFile;
+ private Document document;
+
+ private static String pluginsLocation = null;
+
+
+ public static synchronized NormalActionPlugins getInstance()
+ {
+ if(instance == null)
+ {
+ instance = new NormalActionPlugins();
+ }
+
+ return instance;
+ }
+
+ public static void setPluginsLocation(String location)
+ {
+ pluginsLocation = location;
+ }
+
+
+ private NormalActionPlugins()
+ {
+ logger = Logger.getLogger(NormalActionPlugins.class.getName());
+ normalPluginsHashmap = new HashMap<>();
+ }
+
+ public void init() throws SevereException, MinorException
+ {
+ registerPlugins();
+ initPlugins();
+ }
+
+ public List<NormalAction> getPlugins()
+ {
+ return normalPlugins;
+ }
+
+ public NormalAction getPluginByModuleName(String name)
+ {
+ logger.info("Plugin being requested : "+name);
+ Integer index = normalPluginsHashmap.getOrDefault(name, -1);
+ if(index != -1)
+ {
+ return normalPlugins.get(index);
+ }
+
+ return null;
+ }
+
+ private List<NormalAction> normalPlugins;
+ HashMap<String, Integer> normalPluginsHashmap;
+
+ public void registerPlugins() throws SevereException, MinorException
+ {
+ logger.info("Registering external plugins from "+pluginsLocation+" ...");
+
+ try
+ {
+ configFile = new File(pluginsLocation+"/config.xml");
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+ document = docBuilder.parse(configFile);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Plugins","Error reading plugins config.xml. Cannot continue.");
+ }
+
+ ArrayList<NormalAction> errorModules = new ArrayList<>();
+
+ ArrayList<Action> pluginsConfigs = new ArrayList<>();
+
+ NodeList actionsNode = document.getElementsByTagName("actions").item(0).getChildNodes();
+
+ for(int i =0;i<actionsNode.getLength();i++)
+ {
+ Node eachActionNode = actionsNode.item(i);
+
+ if(eachActionNode.getNodeType() != Node.ELEMENT_NODE)
+ continue;
+
+ if(!eachActionNode.getNodeName().equals("action"))
+ continue;
+
+ Element eachActionElement = (Element) eachActionNode;
+
+
+
+ String name="";
+ Version version;
+ try
+ {
+ name = XMLConfigHelper.getStringProperty(eachActionElement, "module-name");
+ version = new Version(XMLConfigHelper.getStringProperty(eachActionElement, "version"));
+ }
+ catch (Exception e)
+ {
+ logger.log(Level.WARNING, "Skipping configuration because invalid ...");
+ e.printStackTrace();
+ continue;
+ }
+
+ ServerProperties serverProperties = new ServerProperties();
+
+ NodeList serverPropertiesNodeList = eachActionElement.getElementsByTagName("properties").item(0).getChildNodes();
+
+ for(int j = 0;j<serverPropertiesNodeList.getLength();j++)
+ {
+ Node eachPropertyNode = serverPropertiesNodeList.item(j);
+
+
+ if(eachPropertyNode.getNodeType() != Node.ELEMENT_NODE)
+ continue;
+
+ if(!eachPropertyNode.getNodeName().equals("property"))
+ continue;
+
+
+ Element eachPropertyElement = (Element) eachPropertyNode;
+
+ try
+ {
+ Property property = new Property(XMLConfigHelper.getStringProperty(eachPropertyElement, "name"), Type.STRING);
+ property.setRawValue(XMLConfigHelper.getStringProperty(eachPropertyElement, "value"));
+
+ serverProperties.addProperty(property);
+ }
+ catch (Exception e)
+ {
+ logger.log(Level.WARNING, "Skipping property because invalid ...");
+ e.printStackTrace();
+ }
+ }
+
+ Action action = new Action(ActionType.NORMAL);
+
+ action.setModuleName(name);
+ action.setVersion(version);
+ action.getServerProperties().set(serverProperties);
+
+ pluginsConfigs.add(action);
+ }
+
+ logger.info("Size : "+pluginsConfigs.size());
+
+ Path pluginsDir = Paths.get(pluginsLocation); // Directory with plugins JARs
+ try
+ {
+ // Search for plugins in the plugins directory
+ ModuleFinder pluginsFinder = ModuleFinder.of(pluginsDir);
+
+ // Find all names of all found plugin modules
+ List<String> p = pluginsFinder
+ .findAll()
+ .stream()
+ .map(ModuleReference::descriptor)
+ .map(ModuleDescriptor::name)
+ .collect(Collectors.toList());
+
+ // Create configuration that will resolve plugin modules
+ // (verify that the graph of modules is correct)
+ Configuration pluginsConfiguration = ModuleLayer
+ .boot()
+ .configuration()
+ .resolve(pluginsFinder, ModuleFinder.of(), p);
+
+ // Create a module layer for plugins
+ ModuleLayer layer = ModuleLayer
+ .boot()
+ .defineModulesWithOneLoader(pluginsConfiguration, ClassLoader.getSystemClassLoader());
+
+ logger.info("Loading plugins from jar ...");
+ // Now you can use the new module layer to find service implementations in it
+ normalPlugins = ServiceLoader
+ .load(layer, NormalAction.class).stream()
+ .map(ServiceLoader.Provider::get)
+ .collect(Collectors.toList());
+
+ logger.info("...Done!");
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+
+ throw new MinorException("Error", "Error loading modules\n"+e.getMessage()+"\nPlease fix the errors. Other plugins wont be loaded.");
+ }
+
+
+ sortedPlugins = new HashMap<>();
+
+ for (NormalAction eachPlugin : normalPlugins) {
+ try {
+ eachPlugin.setPropertySaver(propertySaver);
+ eachPlugin.setServerConnection(serverConnection);
+ eachPlugin.initProperties();
+
+ Action foundAction = null;
+ for (Action action : pluginsConfigs) {
+ if (action.getModuleName().equals(eachPlugin.getModuleName())
+ && action.getVersion().isEqual(eachPlugin.getVersion())) {
+
+ foundAction = action;
+
+ List<Property> eachPluginStoredProperties = action.getServerProperties().get();
+ List<Property> eachPluginCodeProperties = eachPlugin.getServerProperties().get();
+
+
+ for (int i =0;i< eachPluginCodeProperties.size(); i++) {
+
+ Property eachPluginCodeProperty = eachPluginCodeProperties.get(i);
+
+ Property foundProp = null;
+ for (Property eachPluginStoredProperty : eachPluginStoredProperties) {
+ if (eachPluginCodeProperty.getName().equals(eachPluginStoredProperty.getName())) {
+ eachPluginCodeProperty.setRawValue(eachPluginStoredProperty.getRawValue());
+ foundProp = eachPluginStoredProperty;
+ }
+ }
+
+ eachPluginCodeProperties.set(i, eachPluginCodeProperty);
+
+ if (foundProp != null) {
+ eachPluginStoredProperties.remove(foundProp);
+ }
+ }
+
+
+ eachPlugin.getServerProperties().set(eachPluginCodeProperties);
+
+ break;
+ }
+ }
+
+ if (foundAction != null)
+ pluginsConfigs.remove(foundAction);
+ else
+ {
+ List<Property> eachPluginStoredProperties = eachPlugin.getServerProperties().get();
+ for(Property property :eachPluginStoredProperties)
+ {
+ if(property.getType() == Type.STRING || property.getType() == Type.INTEGER || property.getType() == Type.DOUBLE)
+ property.setRawValue(property.getDefaultRawValue());
+ }
+ }
+
+
+
+ if (!sortedPlugins.containsKey(eachPlugin.getCategory())) {
+ ArrayList<NormalAction> actions = new ArrayList<>();
+
+ sortedPlugins.put(eachPlugin.getCategory(), actions);
+ }
+
+ sortedPlugins.get(eachPlugin.getCategory()).add(eachPlugin);
+
+ /*logger.debug("-----Custom Plugin Debug-----" +
+ "\nAction Type : " + eachPlugin.getActionType() +
+ "\nName : " + eachPlugin.getName() +
+ "\nFull Module Name : " + eachPlugin.getModuleName() +
+ "\nAuthor : " + eachPlugin.getAuthor() +
+ "\nCategory : " + eachPlugin.getCategory() +
+ "\nRepo : " + eachPlugin.getRepo() +
+ "\nVersion : " + eachPlugin.getVersion().getText() +
+ "\n---------------------------");*/
+
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ errorModules.add(eachPlugin);
+ }
+ }
+
+ try {
+ saveServerSettings();
+ } catch (MinorException e) {
+ e.printStackTrace();
+ }
+
+ logger.log(Level.INFO, "All plugins registered!");
+
+ if(errorModules.size() > 0)
+ {
+ StringBuilder errors = new StringBuilder("The following action modules could not be loaded:");
+ for(NormalAction e : errorModules)
+ {
+ normalPlugins.remove(e);
+ errors.append("\n * ").append(e);
+ }
+
+ throw new MinorException("Plugins", errors.toString());
+ }
+
+
+ for(int i = 0;i<normalPlugins.size();i++)
+ {
+ normalPluginsHashmap.put(normalPlugins.get(i).getModuleName(), i);
+ }
+ }
+
+ public void initPlugins() throws MinorException
+ {
+ StringBuilder errors = new StringBuilder("There were errors registering the following plugins. As a result, they have been omitted : ");
+ boolean isError = false;
+
+ for(NormalAction eachPlugin : normalPlugins)
+ {
+ try
+ {
+ eachPlugin.initAction();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ isError = true;
+ errors.append("\n* ")
+ .append(eachPlugin.getName())
+ .append(" - ")
+ .append(eachPlugin.getModuleName())
+ .append("\n");
+
+ if(e instanceof StreamPiException)
+ errors.append(((MinorException) e).getShortMessage());
+
+ errors.append("\n");
+ }
+ }
+
+ if(isError)
+ {
+ throw new MinorException("Plugin init error", errors.toString());
+ }
+ }
+
+ HashMap<String, ArrayList<NormalAction>> sortedPlugins;
+
+ public HashMap<String, ArrayList<NormalAction>> getSortedPlugins()
+ {
+ return sortedPlugins;
+ }
+
+ private Element getActionsElement()
+ {
+ return (Element) document.getElementsByTagName("actions").item(0);
+ }
+
+ public void saveServerSettings() throws MinorException
+ {
+ XMLConfigHelper.removeChilds(getActionsElement());
+
+ for(NormalAction normalAction : normalPlugins)
+ {
+ Element actionElement = document.createElement("action");
+ getActionsElement().appendChild(actionElement);
+
+ Element moduleNameElement = document.createElement("module-name");
+ moduleNameElement.setTextContent(normalAction.getModuleName());
+ actionElement.appendChild(moduleNameElement);
+
+
+ Element versionElement = document.createElement("version");
+ versionElement.setTextContent(normalAction.getVersion().getText());
+ actionElement.appendChild(versionElement);
+
+ Element propertiesElement = document.createElement("properties");
+ actionElement.appendChild(propertiesElement);
+
+ for(String key : normalAction.getServerProperties().getNames())
+ {
+ for(Property eachProperty : normalAction.getServerProperties().getMultipleProperties(key))
+ {
+ Element propertyElement = document.createElement("property");
+ propertiesElement.appendChild(propertyElement);
+
+ Element nameElement = document.createElement("name");
+ nameElement.setTextContent(eachProperty.getName());
+ propertyElement.appendChild(nameElement);
+
+ Element valueElement = document.createElement("value");
+ valueElement.setTextContent(eachProperty.getRawValue());
+ propertyElement.appendChild(valueElement);
+ }
+ }
+ }
+
+ save();
+ }
+
+ private PropertySaver propertySaver = null;
+
+ public void setPropertySaver(PropertySaver propertySaver)
+ {
+ this.propertySaver = propertySaver;
+ }
+
+ private ServerConnection serverConnection = null;
+
+ public void setServerConnection(ServerConnection serverConnection)
+ {
+ this.serverConnection = serverConnection;
+ }
+
+
+ public NormalAction getActionFromIndex(int index)
+ {
+ return normalPlugins.get(index);
+ }
+
+ public void shutDownActions()
+ {
+ if(normalPlugins != null)
+ {
+ for(NormalAction eachPlugin : normalPlugins)
+ {
+ try
+ {
+ eachPlugin.onShutDown();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ normalPlugins.clear();
+ }
+ }
+
+ public void save() throws MinorException
+ {
+ try
+ {
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ Result output = new StreamResult(configFile);
+ Source input = new DOMSource(document);
+
+ transformer.transform(input, output);
+ }
+ catch (Exception e)
+ {
+ throw new MinorException("Config", "unable to save server plugins settings");
+ }
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Client/Client.java
@@ -0,0 +1,219 @@
+package com.StreamPi.Server.Client;
+
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import com.StreamPi.Util.Version.Version;
+import javafx.geometry.Dimension2D;
+
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+public class Client {
+ private String nickName;
+ private final SocketAddress remoteSocketAddress;
+ private final Platform platform;
+ private ClientConnection connectionHandler;
+ private final Version version;
+ private final Version commAPIVersion;
+ private final Version themeAPIVersion;
+ private final ReleaseStatus releaseStatus;
+
+ private double startupDisplayHeight, startupDisplayWidth;
+
+ private final HashMap<String,ClientProfile> profiles;
+
+ private final HashMap<String,ClientTheme> themes;
+
+ private String defaultProfileID;
+ private String defaultThemeFullName;
+
+ private int totalNoOfProfiles;
+
+ public Client(Version version, ReleaseStatus releaseStatus, Version commAPIVersion, Version themeAPIVersion, String nickName, Platform platform, SocketAddress remoteSocketAddress)
+ {
+ this.version = version;
+ this.releaseStatus = releaseStatus;
+ this.commAPIVersion = commAPIVersion;
+ this.themeAPIVersion = themeAPIVersion;
+ this.nickName = nickName;
+ this.remoteSocketAddress = remoteSocketAddress;
+ this.platform = platform;
+ this.profiles = new HashMap<>();
+ this.themes = new HashMap<>();
+ }
+
+ public ReleaseStatus getReleaseStatus() {
+ return releaseStatus;
+ }
+
+ public void setDefaultThemeFullName(String defaultThemeFullName) {
+ this.defaultThemeFullName = defaultThemeFullName;
+ }
+
+ public String getDefaultThemeFullName() {
+ return defaultThemeFullName;
+ }
+
+ public void setTotalNoOfProfiles(int totalNoOfProfiles)
+ {
+ this.totalNoOfProfiles = totalNoOfProfiles;
+ }
+
+ public int getTotalNoOfProfiles()
+ {
+ return totalNoOfProfiles;
+ }
+
+ public void setDefaultProfileID(String ID)
+ {
+ defaultProfileID = ID;
+ }
+
+ public void addTheme(ClientTheme clientTheme) throws CloneNotSupportedException
+ {
+ themes.put(clientTheme.getThemeFullName(), (ClientTheme) clientTheme.clone());
+ }
+
+ public ArrayList<ClientTheme> getThemes()
+ {
+ ArrayList<ClientTheme> clientThemes = new ArrayList<>();
+ for(String clientTheme : themes.keySet())
+ {
+ clientThemes.add(themes.get(clientTheme));
+ }
+ return clientThemes;
+ }
+
+ public ClientTheme getThemeByFullName(String fullName)
+ {
+ return themes.getOrDefault(fullName, null);
+ }
+
+ public String getDefaultProfileID()
+ {
+ return defaultProfileID;
+ }
+
+ public void setConnectionHandler(ClientConnection connectionHandler)
+ {
+ this.connectionHandler = connectionHandler;
+ }
+
+ public ClientConnection getConnectionHandler()
+ {
+ return connectionHandler;
+ }
+
+ //Client Profiles
+
+ /*public ArrayList<ClientProfile> getProfiles()
+ {
+ return profiles;
+ }*/
+
+ public void setNickName(String nickName)
+ {
+ this.nickName = nickName;
+ }
+
+ public List<ClientProfile> getAllClientProfiles()
+ {
+ ArrayList<ClientProfile> clientProfiles = new ArrayList<>();
+ for(String profile : profiles.keySet())
+ clientProfiles.add(profiles.get(profile));
+ return clientProfiles;
+ }
+
+ public void removeProfileFromID(String ID) throws MinorException {
+ profiles.remove(ID);
+ }
+
+ public void addProfile(ClientProfile clientProfile) throws CloneNotSupportedException {
+ profiles.put(clientProfile.getID(), (ClientProfile) clientProfile.clone());
+ }
+
+ public synchronized ClientProfile getProfileByID(String ID) {
+ return profiles.getOrDefault(ID, null);
+ }
+
+ public SocketAddress getRemoteSocketAddress()
+ {
+ return remoteSocketAddress;
+ }
+
+ public Platform getPlatform()
+ {
+ return platform;
+ }
+
+ public String getNickName()
+ {
+ return nickName;
+ }
+
+ public Version getVersion()
+ {
+ return version;
+ }
+
+ public Version getCommAPIVersion()
+ {
+ return commAPIVersion;
+ }
+
+ public Version getThemeAPIVersion()
+ {
+ return themeAPIVersion;
+ }
+
+ public double getStartupDisplayHeight()
+ {
+ return startupDisplayHeight;
+ }
+
+ public double getStartupDisplayWidth()
+ {
+ return startupDisplayWidth;
+ }
+
+ public void setStartupDisplayHeight(double height)
+ {
+ startupDisplayHeight = height;
+ }
+
+ public void setStartupDisplayWidth(double width)
+ {
+ startupDisplayWidth = width;
+ }
+
+ public void debugPrint()
+ {
+ System.out.println("Client Info : "+
+ "\nNickname : "+nickName+
+ "\nRemote address : "+remoteSocketAddress+
+ "\nPlatform : "+platform.getUIName()+
+ "\nVersion : "+version.getText()+
+ "\nComm API Version : "+commAPIVersion.getText()+
+ "\nTheme API Version : "+themeAPIVersion.getText()+
+ "\nDisplay Width : "+startupDisplayWidth+
+ "\nDisplay Height : "+startupDisplayHeight+
+ "\nDefault Profile ID : "+defaultProfileID);
+ }
+
+ private int getMaxRows(int eachActionSize)
+ {
+ return (int) (startupDisplayHeight / eachActionSize);
+ }
+
+ public int getMaxCols(int eachActionSize)
+ {
+ return (int) (startupDisplayWidth / eachActionSize);
+ }
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Client/ClientProfile.java
@@ -0,0 +1,127 @@
+package com.StreamPi.Server.Client;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.Server.Action.NormalActionPlugins;
+import com.StreamPi.Server.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.Util.Exception.MinorException;
+import javafx.geometry.Dimension2D;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.UUID;
+
+public class ClientProfile implements Cloneable {
+
+ private String name, ID;
+
+ private int rows, cols, actionSize, actionGap;
+
+ private final HashMap<String, Action> actions;
+
+ public ClientProfile(String name, String ID, int rows, int cols, int actionSize, int actionGap)
+ {
+ this.actions = new HashMap<>();
+ this.ID = ID;
+ this.name = name;
+ this.rows = rows;
+ this.cols = cols;
+ this.actionGap = actionGap;
+ this.actionSize = actionSize;
+ }
+
+ public ClientProfile(String name, int rows, int cols, int actionSize, int actionGap)
+ {
+ this(name, UUID.randomUUID().toString(), rows, cols, actionSize, actionGap);
+ }
+
+ public Action getActionByID(String ID)
+ {
+ return actions.get(ID);
+ }
+
+ public void removeActionByID(String ID)
+ {
+ actions.remove(ID);
+ }
+
+
+ public Set<String> getActionsKeySet() {
+ return actions.keySet();
+ }
+
+ public synchronized void addAction(Action action) throws CloneNotSupportedException {
+ actions.put(action.getID(), action.clone());
+ }
+
+ public String getID()
+ {
+ return ID;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public int getRows()
+ {
+ return rows;
+ }
+
+ public int getCols()
+ {
+ return cols;
+ }
+
+ public int getActionSize()
+ {
+ return actionSize;
+ }
+
+ public int getActionGap()
+ {
+ return actionGap;
+ }
+
+ public void setRows(int rows)
+ {
+ this.rows = rows;
+ }
+
+ public void setCols(int cols)
+ {
+ this.cols = cols;
+ }
+
+ public void setID(String ID)
+ {
+ this.ID = ID;
+ }
+
+ public void setActionSize(int actionSize)
+ {
+ this.actionSize = actionSize;
+ }
+
+ public void setActionGap(int actionGap)
+ {
+ this.actionGap = actionGap;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+
+ public Object clone() throws CloneNotSupportedException
+ {
+ return super.clone();
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Client/ClientTheme.java
@@ -0,0 +1,42 @@
+package com.StreamPi.Server.Client;
+
+public class ClientTheme implements Cloneable
+{
+ public String themeFullName;
+ public String shortName;
+ public String author;
+ public String version;
+
+ public ClientTheme(String themeFullName, String shortName,
+ String author, String version)
+ {
+ this.themeFullName = themeFullName;
+ this.shortName = shortName;
+ this.author = author;
+ this.version = version;
+ }
+
+ public String getThemeFullName()
+ {
+ return themeFullName;
+ }
+
+ public String getShortName()
+ {
+ return shortName;
+ }
+
+ public String getAuthor()
+ {
+ return author;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public Object clone() throws CloneNotSupportedException
+ {
+ return super.clone();
+ }
+}
\ No newline at end of file
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Connection/ClientConnection.java
@@ -0,0 +1,897 @@
+package com.StreamPi.Server.Connection;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.ActionAPI.OtherActions.CombineAction;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+import com.StreamPi.Server.Action.NormalActionPlugins;
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Client.ClientTheme;
+import com.StreamPi.Server.Info.ServerInfo;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import com.StreamPi.Util.Version.Version;
+import javafx.concurrent.Task;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+public class ClientConnection extends Thread{
+ private Socket socket;
+ private ServerListener serverListener;
+ private AtomicBoolean stop = new AtomicBoolean(false);
+
+ private DataInputStream dis;
+ private DataOutputStream dos;
+
+ private Logger logger;
+
+ private Client client = null;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ public ClientConnection(Socket socket, ServerListener serverListener, ExceptionAndAlertHandler exceptionAndAlertHandler)
+ {
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ //actionIconsToBeSent = new ArrayList__();
+ this.socket = socket;
+
+ this.serverListener = serverListener;
+
+ logger = Logger.getLogger(ClientConnection.class.getName());
+
+ try
+ {
+ dis = new DataInputStream(socket.getInputStream());
+ dos = new DataOutputStream(socket.getOutputStream());
+ } catch (IOException e) {
+ e.printStackTrace();
+
+ exceptionAndAlertHandler.handleMinorException(new MinorException("Unable to start socket streams"));
+ }
+
+ start();
+ }
+
+ public synchronized void exit()
+ {
+ if(stop.get())
+ return;
+
+ logger.info("Stopping ...");
+
+ try
+ {
+ if(socket !=null)
+ {
+ logger.info("Stopping connection "+socket.getRemoteSocketAddress());
+ disconnect();
+ }
+ }
+ catch (SevereException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ }
+
+ public synchronized void exitAndRemove()
+ {
+ exit();
+ removeConnection();
+ serverListener.clearTemp();
+ }
+
+ public void removeConnection()
+ {
+ ClientConnections.getInstance().removeConnection(this);
+ }
+
+
+ public void writeToStream(String text) throws SevereException
+ {
+ /*try
+ {
+ logger.debug(text);
+ dos.writeUTF(text);
+ dos.flush();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }*/
+
+ try
+ {
+ byte[] txtBytes = text.getBytes();
+
+ Thread.sleep(50);
+ dos.writeUTF("string:: ::");
+ dos.flush();
+ dos.writeInt(txtBytes.length);
+ dos.flush();
+ write(txtBytes);
+ dos.flush();
+ }
+ catch (IOException | InterruptedException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }
+
+ }
+
+ public void sendIcon(String profileID, String actionID, byte[] icon) throws SevereException
+ {
+ try
+ {
+ logger.info("Sending action Icon...");
+ //Thread.sleep(50);
+ System.out.println("1");
+ dos.writeUTF("action_icon::"+profileID+"!!"+actionID+"!!::"+icon.length);
+
+ System.out.println("2");
+ dos.flush();
+
+ System.out.println("3");
+ dos.writeInt(icon.length);
+
+ System.out.println("4");
+ dos.flush();
+
+ System.out.println("5");
+ write(icon);
+
+ System.out.println("6");
+ dos.flush();
+
+ System.out.println("7");
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }
+ }
+
+ public void write(byte[] array) throws SevereException
+ {
+ try
+ {
+ dos.write(array);
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to write to IO Stream!");
+ }
+ }
+
+
+ public void initAfterConnectionQueryReceive(String[] arr) throws StreamPiException
+ {
+ logger.info("Setting up Client object ...");
+
+ Version clientVersion;
+ Version commsStandard;
+ Version themesStandard;
+
+ ReleaseStatus releaseStatus;
+
+ try
+ {
+ clientVersion = new Version(arr[1]);
+ releaseStatus = ReleaseStatus.valueOf(arr[2]);
+ commsStandard = new Version(arr[3]);
+ themesStandard = new Version(arr[4]);
+ }
+ catch (MinorException e)
+ {
+ exitAndRemove();
+ throw new MinorException(e.getShortMessage()+" (Client '"+socket.getRemoteSocketAddress()+"' )");
+ }
+
+ if(!commsStandard.isEqual(ServerInfo.getInstance().getCommAPIVersion()))
+ {
+ String errTxt = "Server and Client Communication standards do not match. Make sure you are on the latest version of server and client.\n" +
+ "Server Comms. Standard : "+ServerInfo.getInstance().getCommAPIVersion().getText()+
+ "\nClient Comms. Standard : "+commsStandard.getText();
+
+ disconnect(errTxt);
+ throw new MinorException(errTxt);
+ }
+
+ client = new Client(clientVersion, releaseStatus, commsStandard, themesStandard, arr[5], Platform.valueOf(arr[8]), socket.getRemoteSocketAddress());
+
+ client.setStartupDisplayWidth(Double.parseDouble(arr[6]));
+ client.setStartupDisplayHeight(Double.parseDouble(arr[7]));
+ client.setDefaultProfileID(arr[9]);
+ client.setDefaultThemeFullName(arr[10]);
+
+ client.debugPrint();
+
+ //call get profiles command.
+ serverListener.clearTemp();
+ }
+
+ public synchronized Client getClient()
+ {
+ return client;
+ }
+
+ @Override
+ public void run() {
+
+ try {
+ initAfterConnectionQuerySend();
+ } catch (SevereException e) {
+ e.printStackTrace();
+
+ exceptionAndAlertHandler.handleSevereException(e);
+
+ exitAndRemove();
+ return;
+ }
+
+ try
+ {
+ while(!stop.get())
+ {
+ String msg = "";
+
+ try
+ {
+ String raw = dis.readUTF();
+
+ int length = dis.readInt();
+
+ System.out.println("SIZE TO READ : "+length);
+
+ String[] precursor = raw.split("::");
+
+ String inputType = precursor[0];
+ String secondArg = precursor[1];
+
+
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+
+ /*int count;
+ int chunkSize = 512;
+ while (length>0)
+ {
+ if(chunkSize > length)
+ chunkSize = length;
+ else
+ chunkSize = 512;
+
+ byte[] buffer = new byte[chunkSize];
+ count = dis.read(buffer);
+
+ System.out.println(count);
+
+ byteArrayOutputStream.write(buffer);
+
+ length-=count;
+ }*/
+
+ /*byte[] buffer = new byte[8192];
+ int read;
+ while((read = dis.read(buffer)) != -1){
+ System.out.println("READ : "+read);
+ byteArrayOutputStream.write(buffer, 0, read);
+ }
+
+ System.out.println("READ : "+byteArrayOutputStream.size());
+
+ byteArrayOutputStream.close();
+
+ byte[] bArr = byteArrayOutputStream.toByteArray();*/
+
+ byte[] bArr = new byte[length];
+
+ dis.readFully(bArr);
+
+ if(inputType.equals("string"))
+ {
+ msg = new String(bArr);
+ }
+ else if(inputType.equals("action_icon"))
+ {
+ String[] secondArgSep = secondArg.split("!!");
+
+ String profileID = secondArgSep[0];
+ String actionID = secondArgSep[1];
+
+ getClient().getProfileByID(profileID).getActionByID(actionID).setIcon(bArr);
+
+ //serverListener.clearTemp();
+ continue;
+ }
+ }
+ catch (IOException e)
+ {
+ logger.log(Level.SEVERE, e.getMessage());
+ e.printStackTrace();
+
+ serverListener.clearTemp();
+
+ if(!stop.get())
+ {
+ removeConnection();
+ throw new MinorException("Accidentally disconnected from "+getClient().getNickName()+".");
+ }
+
+ exitAndRemove();
+
+ return;
+ }
+
+ logger.info("Received text : '"+msg+"'");
+
+ String[] sep = msg.split("::");
+
+ String command = sep[0];
+
+ switch (command)
+ {
+ case "disconnect" : clientDisconnected(msg);
+ break;
+
+ case "client_details" : initAfterConnectionQueryReceive(sep);
+ getProfilesFromClient();
+ getThemesFromClient();
+ break;
+
+ case "profiles" : registerProfilesFromClient(sep);
+ break;
+
+ case "profile_details" : registerProfileDetailsFromClient(sep);
+ break;
+
+ case "action_details" : registerActionToProfile(sep);
+ break;
+
+ case "themes": registerThemes(sep);
+ break;
+
+ case "action_clicked": actionClicked(sep[1], sep[2]);
+ break;
+
+ default: logger.warning("Command '"+command+"' does not match records. Make sure client and server versions are equal.");
+
+
+ }
+ }
+ }
+ catch (StreamPiException e)
+ {
+ e.printStackTrace();
+
+
+ if(e instanceof MinorException)
+ exceptionAndAlertHandler.handleMinorException((MinorException) e);
+ else if (e instanceof SevereException)
+ exceptionAndAlertHandler.handleSevereException((SevereException) e);
+
+ }
+ }
+
+
+
+
+
+
+ // commands
+
+ public void initAfterConnectionQuerySend() throws SevereException
+ {
+ logger.info("Asking client details ...");
+ writeToStream("get_client_details::");
+ }
+
+ public void disconnect() throws SevereException {
+ disconnect("");
+ }
+
+ public void disconnect(String message) throws SevereException {
+ if(stop.get())
+ return;
+
+ stop.set(true);
+
+ logger.info("Sending client disconnect message ...");
+ writeToStream("disconnect::"+message+"::");
+
+
+ try
+ {
+ if(!socket.isClosed())
+ socket.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Unable to close socket");
+ }
+ }
+
+ public void clientDisconnected(String message)
+ {
+ stop.set(true);
+ String txt = "Disconnected!";
+
+ if(!message.equals("disconnect::::"))
+ txt = "Message : "+message.split("::")[1];
+
+ new StreamPiAlert("Disconnected from "+getClient().getNickName()+".", txt, StreamPiAlertType.WARNING).show();;
+ exitAndRemove();
+ }
+
+ public void getProfilesFromClient() throws StreamPiException
+ {
+ logger.info("Asking client to send profiles ...");
+ writeToStream("get_profiles::");
+ }
+
+ public void getThemesFromClient() throws StreamPiException
+ {
+ logger.info("Asking clients to send themes ...");
+ writeToStream("get_themes::");
+ }
+
+ public void registerThemes(String[] sep)
+ {
+ for(int i =1; i<sep.length;i++)
+ {
+ String[] internal = sep[i].split("__");
+
+ ClientTheme clientTheme = new ClientTheme(
+ internal[0],
+ internal[1],
+ internal[2],
+ internal[3]
+ );
+
+ try
+ {
+ getClient().addTheme(clientTheme);
+ }
+ catch (CloneNotSupportedException e)
+ {
+ logger.log(Level.SEVERE, e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void registerProfilesFromClient(String[] sep) throws StreamPiException
+ {
+ logger.info("Registering profiles ...");
+
+ int noOfProfiles = Integer.parseInt(sep[1]);
+ getClient().setTotalNoOfProfiles(noOfProfiles);
+
+ for(int i = 2; i<(noOfProfiles + 2); i++)
+ {
+ String profileID = sep[i];
+ getProfileDetailsFromClient(profileID);
+ }
+ }
+
+ public void getProfileDetailsFromClient(String ID) throws StreamPiException
+ {
+ logger.info("Asking client to send details of profile : "+ID);
+ writeToStream("get_profile_details::"+ID+"::");
+ }
+
+
+ public void registerProfileDetailsFromClient(String[] sep)
+ {
+ String ID = sep[1];
+ logger.info("Registering details for profile : "+ID);
+
+ String name = sep[2];
+ int rows = Integer.parseInt(sep[3]);
+ int cols = Integer.parseInt(sep[4]);
+ int actionSize = Integer.parseInt(sep[5]);
+ int actionGap = Integer.parseInt(sep[6]);
+
+
+ ClientProfile clientProfile = new ClientProfile(name, ID, rows, cols, actionSize, actionGap);
+
+ logger.info("Added client profile "+clientProfile.getName());
+ try
+ {
+ getClient().addProfile(clientProfile);
+ }
+ catch (CloneNotSupportedException e)
+ {
+ logger.severe(e.getMessage());
+ e.printStackTrace();
+ }
+ serverListener.clearTemp();
+ }
+
+ /*public void getActionIcon(String profileID, String actionID) throws StreamPiException
+ {
+ System.out.println("getting action icon from "+profileID+", "+actionID);
+ writeToStream("get_action_icon::"+profileID+"::"+actionID);
+ }*/
+
+ public synchronized void registerActionToProfile(String[] sep) throws StreamPiException
+ {
+ String profileID = sep[1];
+
+ String ID = sep[2];
+ ActionType actionType = ActionType.valueOf(sep[3]);
+
+ //4 - Version
+ //5 - ModuleName
+
+ //display
+ String bgColorHex = sep[6];
+
+ //icon
+ boolean isHasIcon = sep[7].equals("true");
+ boolean isShowIcon = sep[8].equals("true");
+
+ //text
+ boolean isShowDisplayText = sep[9].equals("true");
+ String displayFontColor = sep[10];
+ String displayText = sep[11];
+ DisplayTextAlignment displayTextAlignment = DisplayTextAlignment.valueOf(sep[12]);
+
+ //location
+ String row = sep[13];
+ String col = sep[14];
+
+ Location location = new Location(Integer.parseInt(row), Integer.parseInt(col));
+
+
+
+ Action action = new Action(ID, actionType);
+
+ action.setBgColourHex(bgColorHex);
+ action.setShowIcon(isShowIcon);
+ action.setHasIcon(isHasIcon);
+
+ action.setShowDisplayText(isShowDisplayText);
+ action.setDisplayTextFontColourHex(displayFontColor);
+ action.setDisplayText(displayText);
+ action.setDisplayTextAlignment(displayTextAlignment);
+
+
+ action.setLocation(location);
+
+
+ //client properties
+
+ int clientPropertiesSize = Integer.parseInt(sep[15]);
+
+ String root = sep[17];
+ action.setParent(root);
+
+ String[] clientPropertiesRaw = sep[16].split("!!");
+
+ ClientProperties clientProperties = new ClientProperties();
+
+ if(actionType == ActionType.FOLDER)
+ clientProperties.setDuplicatePropertyAllowed(true);
+
+ for(int i = 0;i<clientPropertiesSize; i++)
+ {
+ String[] clientPraw = clientPropertiesRaw[i].split("__");
+
+ Property property = new Property(clientPraw[0], Type.STRING);
+
+ if(clientPraw.length > 1)
+ property.setRawValue(clientPraw[1]);
+
+ clientProperties.addProperty(property);
+ }
+
+ action.setClientProperties(clientProperties);
+ action.setModuleName(sep[5]);
+
+ //set up action
+
+ //Action toBeAdded = null;
+
+ if(actionType == ActionType.NORMAL)
+ {
+ NormalAction actionCopy = NormalActionPlugins.getInstance().getPluginByModuleName(sep[5]);
+
+ if(actionCopy == null)
+ {
+ action.setInvalid(true);
+ }
+ else
+ {
+ action.setVersion(new Version(sep[4]));
+
+ action.setRepo(actionCopy.getRepo());
+
+ if(actionCopy.getVersion().getMajor() != action.getVersion().getMajor())
+ {
+ action.setInvalid(true);
+ }
+ else
+ {
+ action.setName(actionCopy.getName());
+
+ ClientProperties finalClientProperties = new ClientProperties();
+
+
+ for(Property property : actionCopy.getClientProperties().get())
+ {
+ for(int i = 0;i<action.getClientProperties().getSize();i++)
+ {
+ Property property1 = action.getClientProperties().get().get(i);
+ if (property.getName().equals(property1.getName()))
+ {
+ property.setRawValue(property1.getRawValue());
+
+
+ finalClientProperties.addProperty(property);
+ }
+ }
+ }
+
+ action.setClientProperties(finalClientProperties);
+
+ }
+ }
+ }
+
+
+ try
+ {
+ for(Property prop : action.getClientProperties().get())
+ {
+ logger.info("G@@@@@ : "+prop.getRawValue());
+ }
+
+
+ getClient().getProfileByID(profileID).addAction(action);
+
+
+
+ for(String action1x : getClient().getProfileByID(profileID).getActionsKeySet())
+ {
+ Action action1 = getClient().getProfileByID(profileID).getActionByID(action1x);
+ logger.info("231cc : "+action1.getID());
+ for(Property prop : action1.getClientProperties().get())
+ {
+ logger.info("G@VVVV@@@ : "+prop.getRawValue());
+ }
+ }
+
+ }
+ catch (CloneNotSupportedException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(new MinorException("Action", "Unable to clone"));
+ }
+
+ }
+
+ public void saveActionDetails(String profileID, Action action) throws SevereException
+ {
+ StringBuilder finalQuery = new StringBuilder("save_action_details::");
+
+ finalQuery.append(profileID)
+ .append("::")
+ .append(action.getID())
+ .append("::")
+ .append(action.getActionType())
+ .append("::");
+
+ if(action.getActionType() == ActionType.NORMAL)
+ {
+ finalQuery.append(action.getVersion().getText());
+ System.out.println("VERSION :sdd "+action.getVersion().getText());
+ }
+
+ finalQuery.append("::");
+
+ if(action.getActionType() == ActionType.NORMAL)
+ finalQuery.append(action.getModuleName());
+
+ finalQuery.append("::");
+
+ //display
+
+ finalQuery.append(action.getBgColourHex())
+ .append("::");
+
+ //icon
+ finalQuery.append(action.isHasIcon())
+ .append("::")
+ .append(action.isShowIcon())
+ .append("::");
+
+ //text
+ finalQuery.append(action.isShowDisplayText())
+ .append("::")
+ .append(action.getDisplayTextFontColourHex())
+ .append("::")
+ .append(action.getDisplayText())
+ .append("::")
+ .append(action.getDisplayTextAlignment())
+ .append("::");
+
+ //location
+
+ if(action.getLocation() == null)
+ finalQuery.append("-1::-1::");
+ else
+ finalQuery.append(action.getLocation().getRow())
+ .append("::")
+ .append(action.getLocation().getCol())
+ .append("::");
+
+ //client properties
+
+ ClientProperties clientProperties = action.getClientProperties();
+
+ finalQuery.append(clientProperties.getSize())
+ .append("::");
+
+ for(Property property : clientProperties.get())
+ {
+ finalQuery.append(property.getName())
+ .append("__")
+ .append(property.getRawValue())
+ .append("__");
+
+ finalQuery.append("!!");
+ }
+
+ finalQuery.append("::")
+ .append(action.getParent())
+ .append("::");
+
+ writeToStream(finalQuery.toString());
+
+ }
+
+ public void deleteAction(String profileID, String actionID) throws SevereException
+ {
+ writeToStream("delete_action::"+profileID+"::"+actionID);
+ }
+
+ public void saveClientDetails(String clientNickname, String screenWidth, String screenHeight, String defaultProfileID,
+ String defaultThemeFullName) throws SevereException
+ {
+ writeToStream("save_client_details::"+
+ clientNickname+"::"+
+ screenWidth+"::"+
+ screenHeight+"::"+
+ defaultProfileID+"::"+
+ defaultThemeFullName+"::");
+
+ client.setNickName(clientNickname);
+ client.setStartupDisplayWidth(Double.parseDouble(screenWidth));
+ client.setStartupDisplayHeight(Double.parseDouble(screenHeight));
+ client.setDefaultProfileID(defaultProfileID);
+ client.setDefaultThemeFullName(defaultThemeFullName);
+ }
+
+ public void saveProfileDetails(ClientProfile clientProfile) throws SevereException, CloneNotSupportedException {
+ if(client.getProfileByID(clientProfile.getID()) !=null)
+ {
+ client.getProfileByID(clientProfile.getID()).setName(clientProfile.getName());
+ client.getProfileByID(clientProfile.getID()).setRows(clientProfile.getRows());
+ client.getProfileByID(clientProfile.getID()).setCols(clientProfile.getCols());
+ client.getProfileByID(clientProfile.getID()).setActionSize(clientProfile.getActionSize());
+ client.getProfileByID(clientProfile.getID()).setActionGap(clientProfile.getActionGap());
+ }
+ else
+ client.addProfile(clientProfile);
+
+ writeToStream("save_client_profile::"+
+ clientProfile.getID()+"::"+
+ clientProfile.getName()+"::"+
+ clientProfile.getRows()+"::"+
+ clientProfile.getCols()+"::"+
+ clientProfile.getActionSize()+"::"+
+ clientProfile.getActionGap()+"::");
+ }
+
+ public void deleteProfile(String ID) throws SevereException
+ {
+ writeToStream("delete_profile::"+ID+"::");
+ }
+
+ public void actionClicked(String profileID, String actionID) {
+
+ try
+ {
+ Action action = client.getProfileByID(profileID).getActionByID(actionID);
+
+ if(action.getActionType() == ActionType.NORMAL)
+ {
+ NormalAction original = NormalActionPlugins.getInstance().getPluginByModuleName(
+ action.getModuleName()
+ );
+
+ if(original == null)
+ {
+ throw new MinorException(
+ "The Action isn't installed on the server."
+ );
+ }
+
+ NormalAction normalAction = original.clone();
+
+
+
+ normalAction.setLocation(action.getLocation());
+ normalAction.setDisplayText(action.getDisplayText());
+ normalAction.setID(actionID);
+
+ normalAction.setClientProperties(action.getClientProperties());
+
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call()
+ {
+ try
+ {
+ boolean result = serverListener.onNormalActionClicked(normalAction);
+ if(!result)
+ {
+ sendActionFailed(profileID, actionID);
+ }
+ }
+ catch (SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ return null;
+ }
+ }).start();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(new MinorException(e.getMessage()));
+ }
+ }
+
+ public void sendActionFailed(String profileID, String actionID) throws SevereException {
+ logger.info("Sending failed status ...");
+ writeToStream("action_failed::"+
+ profileID+"::"+
+ actionID+"::");
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Connection/ClientConnections.java
@@ -0,0 +1,72 @@
+package com.StreamPi.Server.Connection;
+
+
+import com.StreamPi.Server.Client.Client;
+
+import java.net.SocketAddress;
+import java.util.ArrayList;
+
+public class ClientConnections {
+
+ private ArrayList<ClientConnection> connections;
+
+ private static ClientConnections instance = null;
+
+ private ClientConnections()
+ {
+ connections = new ArrayList<>();
+ }
+
+ public static synchronized ClientConnections getInstance()
+ {
+ if(instance == null)
+ {
+ instance = new ClientConnections();
+ }
+
+ return instance;
+ }
+
+ public ArrayList<ClientConnection> getConnections()
+ {
+ return connections;
+ }
+
+ public void clearAllConnections()
+ {
+ connections.clear(); // NOT RECOMMENDED TO USE CARELESSLY
+ }
+
+ public void addConnection(ClientConnection connection)
+ {
+ connections.add(connection);
+ }
+
+ public void removeConnection(ClientConnection clientConnection)
+ {
+ System.out.println(connections.remove(clientConnection)+" 22222222222222222222222222222222222222222");
+ }
+
+ public void disconnectAll()
+ {
+ new Thread(()->{
+ for(ClientConnection clientConnection : connections)
+ {
+ clientConnection.exit();
+ }
+
+ clearAllConnections();
+ }).start();
+ }
+
+ public ClientConnection getClientConnectionBySocketAddress(SocketAddress socketAddress)
+ {
+ for(ClientConnection clientConnection : connections)
+ {
+ if(clientConnection.getClient().getRemoteSocketAddress().equals(socketAddress))
+ return clientConnection;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Connection/ConnectionService.java
@@ -0,0 +1,345 @@
+/*package com.StreamPi.Server.Connection;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.CommAPI.ConnectionGrpc;
+import com.StreamPi.CommAPI.ServerGRPC;
+import com.StreamPi.Server.Action.NormalActionPlugins;
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Info.ServerInfo;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.Version.Version;
+import io.grpc.stub.StreamObserver;
+import javafx.concurrent.Task;
+import javafx.scene.control.Alert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class ConnectionService extends ConnectionGrpc.ConnectionImplBase {
+
+ private Client client = null;
+ private Logger logger;
+
+ ServerListener serverListener;
+
+ final HashMap<String, Boolean> actionStatuses;
+
+ public ConnectionService(ServerListener serverListener)
+ {
+ super();
+
+ isDisconnect = false;
+ disconnectMessage = "";
+
+ logger = LoggerFactory.getLogger(ConnectionService.class);
+ actionStatuses = new HashMap<>();
+
+ this.serverListener = serverListener;
+ }
+
+
+ boolean isDisconnect;
+ String disconnectMessage;
+
+ public void disconnect()
+ {
+ disconnect("");
+ }
+
+ public void disconnect(String message)
+ {
+ if(!isDisconnect)
+ {
+ this.isDisconnect = true;
+ this.disconnectMessage = message;
+ }
+ }
+
+ @Override
+ public void sendClientDetails(ServerGRPC.ClientDetails request, StreamObserver<ServerGRPC.Status> responseObserver) {
+
+ Version clientVersion;
+ Version commsAPIVersion;
+ Version themeAPIVersion;
+
+ try
+ {
+ clientVersion = new Version(request.getClientVersion());
+ commsAPIVersion = new Version(request.getCommAPIVersion());
+ themeAPIVersion = new Version(request.getThemeAPIVersion());
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ serverListener.onRPCError(new MinorException("versions invalid. Check stacktrace"));
+ disconnect();
+ return;
+ }
+
+
+ String nickName = request.getNickName();
+ Platform platform = Platform.valueOf(request.getPlatform().toString());
+
+ client = new Client(clientVersion, commsAPIVersion, themeAPIVersion, nickName, platform, null);
+
+ boolean sendActions = true;
+
+ if(!commsAPIVersion.isEqual(ServerInfo.getInstance().getCommAPIVersion()))
+ {
+ sendActions = false;
+ disconnect("Client CommAPI and Server CommAPI do not match!");
+ }
+
+ responseObserver.onNext(ServerGRPC.Status.newBuilder().setSendActions(sendActions).build());
+
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public StreamObserver<ServerGRPC.ClientProfile> sendClientProfiles(StreamObserver<ServerGRPC.Empty> responseObserver) {
+
+ client.getProfiles().clear();
+
+ ArrayList<String> notFoundActions = new ArrayList<>();
+
+ return new StreamObserver<ServerGRPC.ClientProfile>() {
+ @Override
+ public void onNext(ServerGRPC.ClientProfile clientProfile) {
+
+ String name = clientProfile.getName();
+ String id = clientProfile.getId();
+
+ int rows = clientProfile.getRows();
+ int cols = clientProfile.getCols();
+
+ int actionSize = clientProfile.getActionSize();
+ int actionGap = clientProfile.getActionGap();
+
+ ClientProfile finalClientProfile = new ClientProfile(name, id, rows, cols, actionSize, actionGap);
+
+ ArrayList<Action> actions = new ArrayList<>();
+
+ List<ServerGRPC.ClientAction> clientActions = clientProfile.getActionsList();
+ for(ServerGRPC.ClientAction clientAction : clientActions)
+ {
+
+ String actionID = clientAction.getId();
+ String actionName = clientAction.getActionName();
+
+ boolean hasIcon = clientAction.getHasIcon();
+
+ ActionType actionType = ActionType.valueOf(clientAction.getActionType().toString());
+
+ Action action = new Action(actionID, actionType);
+ action.setActionName(actionName);
+
+ action.setHasIcon(hasIcon);
+
+ int locationX = clientAction.getLocationX();
+ int locationY = clientAction.getLocationY();
+
+ action.setLocation(new Location(locationX, locationY));
+
+ if(actionType == ActionType.NORMAL)
+ {
+ action.setModuleName(clientAction.getModuleName());
+
+ ClientProperties properties = new ClientProperties();
+
+ List<ServerGRPC.ClientProperty> clientProperties = clientAction.getClientPropertiesList();
+
+ for(ServerGRPC.ClientProperty clientProperty : clientProperties)
+ {
+ String propertyName = clientProperty.getName();
+ String propertyValue = clientProperty.getValue();
+
+ properties.addProperty(propertyName, propertyValue);
+ }
+
+ action.setClientProperties(properties);
+
+ boolean isFound = false;
+ for(NormalAction normalAction : NormalActionPlugins.getInstance().getPlugins())
+ {
+ if(normalAction.getModuleName().equals(action.getModuleName()))
+ {
+ isFound = true;
+
+ normalAction.setClientProperties(action.getClientProperties());
+ normalAction.setActionName(action.getActionName());
+ normalAction.setHasIcon(action.isHasIcon());
+ normalAction.setID(action.getID());
+ normalAction.setLocation(action.getLocation());
+ normalAction.setInvalid(false);
+
+ actions.add(normalAction);
+
+ break;
+ }
+ }
+
+ if(!isFound)
+ {
+ String aName = action.getModuleName();
+
+ action.setInvalid(true);
+
+ logger.warn("Action "+aName+" not found!");
+ if(!notFoundActions.contains(aName))
+ notFoundActions.add(aName);
+
+ actions.add(action);
+ }
+ }
+ }
+
+ finalClientProfile.setActions(actions);
+ client.addProfile(finalClientProfile);
+
+
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ serverListener.onRPCError(new SevereException(throwable.getMessage()));
+ }
+
+ @Override
+ public void onCompleted() {
+
+ if(!notFoundActions.isEmpty())
+ {
+ StringBuilder all = new StringBuilder("Some actions cannot be edited/used because they are not installed on the server : ");
+
+ for(String each : notFoundActions)
+ {
+ all.append("\n * ").append(each);
+ }
+
+ serverListener.onAlert("Warning",all.toString(), Alert.AlertType.WARNING);
+ }
+
+ responseObserver.onNext(ServerGRPC.Empty.newBuilder().build());
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ public void actionClicked(ServerGRPC.ClickedActionID request, StreamObserver<ServerGRPC.Empty> responseObserver) {
+ try
+ {
+ Action actionClicked = client.getProfileByID(request.getProfileID()).getActionByID(request.getId());
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call()
+ {
+ try
+ {
+ synchronized (actionStatuses)
+ {
+ actionStatuses.put(request.getId(), serverListener.onActionClicked(actionClicked));
+ }
+ }
+ catch (MinorException e)
+ {
+ serverListener.onRPCError(e);
+ }
+ return null;
+ }
+ }).start();
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ }
+ finally {
+ responseObserver.onNext(ServerGRPC.Empty.newBuilder().build());
+ responseObserver.onCompleted();
+ }
+ }
+
+ @Override
+ public StreamObserver<ServerGRPC.Empty> actionClickedStatus(StreamObserver<ServerGRPC.ActionStatus> responseObserver) {
+ return new StreamObserver<ServerGRPC.Empty>() {
+ @Override
+ public void onNext(ServerGRPC.Empty empty) {
+
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ serverListener.onRPCError(new StreamPiException(throwable.getMessage()));
+ }
+
+ @Override
+ public void onCompleted() {
+ String id = null;
+ boolean success = false;
+ synchronized (actionStatuses)
+ {
+ if(!actionStatuses.isEmpty())
+ {
+ for(String key : actionStatuses.keySet())
+ {
+ id = key;
+ success = actionStatuses.get(id);
+
+ System.out.println("SDSDSDASDASDXZXCZXCZXC");
+ break;
+ }
+ }
+ }
+
+ if(id==null)
+ {
+ responseObserver.onNext(ServerGRPC.ActionStatus.newBuilder().build());
+ }
+ else
+ responseObserver.onNext(ServerGRPC.ActionStatus.newBuilder()
+ .setId(id)
+ .setIsSuccess(success)
+ .build());
+
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ public StreamObserver<ServerGRPC.DisconnectMessage> disconnect(StreamObserver<ServerGRPC.DisconnectMessage> responseObserver) {
+ return new StreamObserver<ServerGRPC.DisconnectMessage>() {
+ @Override
+ public void onNext(ServerGRPC.DisconnectMessage disconnectMessage) {
+ if(disconnectMessage.getIsDisconnect())
+ disconnect(disconnectMessage.getMessage());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ serverListener.onRPCError(new StreamPiException(throwable.getMessage()));
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onNext(ServerGRPC.DisconnectMessage.newBuilder()
+ .setIsDisconnect(isDisconnect)
+ .setMessage(disconnectMessage)
+ .build());
+ responseObserver.onCompleted();
+ }
+ };
+ }
+}
+*/
\ No newline at end of file
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Connection/MainServer.java
@@ -0,0 +1,105 @@
+package com.StreamPi.Server.Connection;
+
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import javafx.concurrent.Task;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+public class MainServer extends Thread{
+ private ServerListener serverListener;
+
+ private Logger logger = Logger.getLogger(MainServer.class.getName());
+ private int port;
+ private ServerSocket serverSocket = null;
+ //private Server server;
+
+
+ private AtomicBoolean stop = new AtomicBoolean(false);
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+ public MainServer(ServerListener serverListener, ExceptionAndAlertHandler exceptionAndAlertHandler)
+ {
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.port = port;
+ this.serverListener = serverListener;
+ }
+
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+
+ @Override
+ public synchronized void start() {
+ stop.set(false);
+ super.start();
+ }
+
+ public void stopListeningForConnections()
+ {
+
+ /*if(server !=null)
+ {
+ if(!server.isShutdown())
+ server.shutdown();
+ }*/
+
+ try
+ {
+ logger.info("Stopping listening for connections ...");
+ if(serverSocket!=null)
+ if(!serverSocket.isClosed())
+ serverSocket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ finally {
+ logger.info("... Done!");
+ }
+ stop.set(true);
+ }
+
+ @Override
+ public void run() {
+ logger.warning("Starting main server on port "+port+" ...");
+
+ try {
+
+ logger.info("Starting server on port "+port+" ...");
+ /*Server server = ServerBuilder.forPort(port)
+ .addService(new ConnectionService(serverListener))
+ .build();
+
+ server.start();
+ logger.info("... Done!");*/
+
+ serverSocket = new ServerSocket(port);
+
+ while(!stop.get())
+ {
+ Socket s = serverSocket.accept();
+ ClientConnections.getInstance().addConnection(new ClientConnection(s, serverListener, exceptionAndAlertHandler));
+
+ logger.info("New Client connected ("+s.getRemoteSocketAddress()+") !");
+ }
+
+ }
+ catch (SocketException e)
+ {
+ logger.info("Main Server stopped accepting calls ...");
+ e.printStackTrace(); //more likely stopped listening;
+ } catch (IOException e) {
+ exceptionAndAlertHandler.handleSevereException(new SevereException("MainServer IO Exception occurred!"));
+ e.printStackTrace();
+ }
+ }
+
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Connection/ServerListener.java
@@ -0,0 +1,21 @@
+package com.StreamPi.Server.Connection;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+import javafx.scene.control.Alert;
+
+import java.net.Socket;
+
+public interface ServerListener {
+ boolean onNormalActionClicked(NormalAction action);
+
+ void clearTemp();
+
+ void init();
+
+ void othInit();
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Controller/Controller.java
@@ -0,0 +1,541 @@
+package com.StreamPi.Server.Controller;
+
+import com.StreamPi.ActionAPI.Action.ServerConnection;
+import com.StreamPi.ActionAPI.Action.PropertySaver;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.Server.Main;
+import com.StreamPi.Server.Action.NormalActionPlugins;
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.Server.Connection.ClientConnections;
+import com.StreamPi.Server.Connection.MainServer;
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.IO.Config;
+import com.StreamPi.Server.Info.ServerInfo;
+import com.StreamPi.Server.Window.Base;
+import com.StreamPi.Server.Window.Dashboard.DonatePopupContent;
+import com.StreamPi.Server.Window.FirstTimeUse.FirstTimeUse;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Alert.StreamPiAlertListener;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.Exception.StreamPiException;
+import com.StreamPi.Util.IOHelper.IOHelper;
+import com.StreamPi.Util.StartAtBoot.SoftwareType;
+import com.StreamPi.Util.StartAtBoot.StartAtBoot;
+
+import javafx.animation.Interpolator;
+import javafx.animation.KeyFrame;
+import javafx.animation.KeyValue;
+import javafx.animation.Timeline;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.control.Alert;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import javafx.stage.WindowEvent;
+import java.awt.SystemTray;
+import javafx.util.Duration;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.awt.PopupMenu;
+import java.awt.MenuItem;
+
+import java.io.File;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.logging.Level;
+
+public class Controller extends Base implements PropertySaver, ServerConnection
+{
+ MainServer mainServer;
+
+ final SystemTray systemTray;
+
+ public void setupDashWindow() throws SevereException
+ {
+ try
+ {
+ getStage().setTitle("StreamPi Server - "+InetAddress.getLocalHost().getCanonicalHostName()+":"+Config.getInstance().getPort()); //Sets title
+ getStage().setOnCloseRequest(this::onCloseRequest);
+ }
+ catch (UnknownHostException e)
+ {
+ e.printStackTrace();
+ throw new SevereException(e.getMessage());
+ }
+ }
+
+ private void checkPrePathDirectory() throws SevereException
+ {
+ try {
+ File filex = new File(ServerInfo.getInstance().getPrePath());
+
+ System.out.println("SAX : "+filex.exists());
+ if(!filex.exists())
+ {
+ filex.mkdirs();
+ IOHelper.unzip(Main.class.getResourceAsStream("Default.obj"), ServerInfo.getInstance().getPrePath());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ throw new SevereException(e.getMessage());
+ }
+ }
+
+ @Override
+ public void init()
+ {
+ try {
+ checkPrePathDirectory();
+
+ initBase();
+ setupDashWindow();
+
+
+ setupSettingsWindowsAnimations();
+
+ NormalActionPlugins.getInstance().setPropertySaver(this);
+ NormalActionPlugins.getInstance().setServerConnection(this);
+
+
+ getDashboardPane().getPluginsPane().getSettingsButton().setOnAction(event -> {
+ openSettingsTimeLine.play();
+ });
+
+ getSettingsPane().getCloseButton().setOnAction(event -> {
+ closeSettingsTimeLine.play();
+ });
+
+ getSettingsPane().getThemesSettings().setController(this);
+
+
+ mainServer = new MainServer(this, this);
+
+
+ if(getConfig().isFirstTimeUse())
+ {
+ Stage stage = new Stage();
+ Scene s = new Scene(new FirstTimeUse(this, this), 600, 500);
+ stage.setScene(s);
+ stage.setTitle("StreamPi Server Setup");
+ stage.initModality(Modality.APPLICATION_MODAL);
+ stage.setOnCloseRequest(event->Platform.exit());
+ stage.show();
+ }
+ else
+ {
+ if(getConfig().isAllowDonatePopup())
+ {
+ if(new Random().nextInt(5) == 3)
+ new DonatePopupContent(getHostServices(), this).show();
+ }
+
+ othInit();
+ }
+
+ }
+ catch (SevereException e)
+ {
+ handleSevereException(e);
+ }
+ }
+
+ @Override
+ public void othInit()
+ {
+ try
+ {
+ if(ServerInfo.getInstance().isStartMinimised())
+ minimiseApp();
+ else
+ getStage().show();
+ }
+ catch(MinorException e)
+ {
+ handleMinorException(e);
+ }
+
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call()
+ {
+ try
+ {
+
+
+
+ getSettingsPane().getGeneralSettings().loadDataFromConfig();
+
+ Platform.runLater(()->{
+ getDashboardPane().getPluginsPane().clearData();
+ getDashboardPane().getPluginsPane().loadOtherActions();
+ });
+
+ NormalActionPlugins.setPluginsLocation(getConfig().getPluginsPath());
+ NormalActionPlugins.getInstance().init();
+ Platform.runLater(()->getDashboardPane().getPluginsPane().loadData());
+
+ getSettingsPane().getPluginsSettings().loadPlugins();
+
+
+ getSettingsPane().getThemesSettings().setThemes(getThemes());
+ getSettingsPane().getThemesSettings().setCurrentThemeFullName(getCurrentTheme().getFullName());
+ getSettingsPane().getThemesSettings().loadThemes();
+
+ getSettingsPane().getClientsSettings().loadData();
+
+ mainServer.setPort(getConfig().getPort());
+ mainServer.start();
+ }
+ catch (MinorException e)
+ {
+ handleMinorException(e);
+ }
+ catch (SevereException e)
+ {
+ handleSevereException(e);
+ }
+ return null;
+ }
+ }).start();
+ }
+
+ private void setupSettingsWindowsAnimations()
+ {
+ Node settingsNode = getSettingsPane();
+ Node dashboardNode = getDashboardPane();
+
+ openSettingsTimeLine = new Timeline();
+ openSettingsTimeLine.setCycleCount(1);
+
+
+ openSettingsTimeLine.getKeyFrames().addAll(
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 0.0D, Interpolator.EASE_IN),
+ new KeyValue(settingsNode.scaleXProperty(),
+ 1.1D, Interpolator.EASE_IN),
+ new KeyValue(settingsNode.scaleYProperty(),
+ 1.1D, Interpolator.EASE_IN),
+ new KeyValue(settingsNode.scaleZProperty(),
+ 1.1D, Interpolator.EASE_IN)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleXProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleYProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleZProperty(),
+ 1.0D, Interpolator.LINEAR)),
+
+
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleXProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleYProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleZProperty(),
+ 1.0D, Interpolator.LINEAR)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 0.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleXProperty(),
+ 0.9D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleYProperty(),
+ 0.9D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleZProperty(),
+ 0.9D, Interpolator.LINEAR))
+ );
+
+ openSettingsTimeLine.setOnFinished(event1 -> {
+ settingsNode.toFront();
+ });
+
+
+ closeSettingsTimeLine = new Timeline();
+ closeSettingsTimeLine.setCycleCount(1);
+
+ closeSettingsTimeLine.getKeyFrames().addAll(
+
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleXProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleYProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleZProperty(),
+ 1.0D, Interpolator.LINEAR)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(settingsNode.opacityProperty(),
+ 0.0D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleXProperty(),
+ 1.1D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleYProperty(),
+ 1.1D, Interpolator.LINEAR),
+ new KeyValue(settingsNode.scaleZProperty(),
+ 1.1D, Interpolator.LINEAR)),
+
+ new KeyFrame(Duration.millis(0.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 0.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleXProperty(),
+ 0.9D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleYProperty(),
+ 0.9D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleZProperty(),
+ 0.9D, Interpolator.LINEAR)),
+ new KeyFrame(Duration.millis(90.0D),
+ new KeyValue(dashboardNode.opacityProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleXProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleYProperty(),
+ 1.0D, Interpolator.LINEAR),
+ new KeyValue(dashboardNode.scaleZProperty(),
+ 1.0D, Interpolator.LINEAR))
+
+ );
+
+ closeSettingsTimeLine.setOnFinished(event1 -> {
+ dashboardNode.toFront();
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call() {
+ try {
+ getSettingsPane().getClientsSettings().loadData();
+
+ getSettingsPane().getGeneralSettings().loadDataFromConfig();
+ getSettingsPane().getPluginsSettings().loadPlugins();
+
+ getSettingsPane().getThemesSettings().setThemes(getThemes());
+ getSettingsPane().getThemesSettings().setCurrentThemeFullName(getCurrentTheme().getFullName());
+ getSettingsPane().getThemesSettings().loadThemes();
+
+ getSettingsPane().setDefaultTabToGeneral();
+ }
+ catch (SevereException e)
+ {
+ handleSevereException(e);
+ }
+ catch (MinorException e)
+ {
+ handleMinorException(e);
+ }
+ return null;
+ }
+ }).start();
+ });
+ }
+
+ private Timeline openSettingsTimeLine;
+ private Timeline closeSettingsTimeLine;
+
+
+ public Controller(){
+ systemTray = SystemTray.getSystemTray();
+ mainServer = null;
+ }
+
+ public void onCloseRequest(WindowEvent event)
+ {
+ try
+ {
+ if(Config.getInstance().getCloseOnX())
+ {
+ getConfig().setStartupWindowSize(
+ getWidth(),
+ getHeight()
+ );
+ getConfig().save();
+ onQuitApp();
+ NormalActionPlugins.getInstance().shutDownActions();
+ Platform.exit();
+ }
+ else
+ {
+ minimiseApp();
+ event.consume();
+ }
+ }
+ catch (SevereException e)
+ {
+ handleSevereException(e);
+ }
+ catch (MinorException e)
+ {
+ handleMinorException(e);
+ }
+ finally
+ {
+ closeLogger();
+ }
+ }
+
+ public void onQuitApp()
+ {
+ if(mainServer!=null)
+ mainServer.stopListeningForConnections();
+
+ ClientConnections.getInstance().disconnectAll();
+
+ getLogger().info("Shutting down ...");
+ }
+
+ public void minimiseApp() throws MinorException
+ {
+ try
+ {
+
+ if(SystemTray.isSupported())
+ {
+ if(getTrayIcon() == null)
+ initIconTray();
+
+ systemTray.add(getTrayIcon());
+ //getStage().setIconified(true);
+ getStage().hide();
+ }
+ else
+ {
+ new StreamPiAlert("System Tray Error", "Your System does not support System Tray", StreamPiAlertType.ERROR).show();
+ }
+ }
+ catch(Exception e)
+ {
+ throw new MinorException(e.getMessage());
+ }
+ }
+
+ public void initIconTray()
+ {
+
+ Platform.setImplicitExit(false);
+
+ PopupMenu popup = new PopupMenu();
+
+ MenuItem showItem = new MenuItem("Show");
+ showItem.addActionListener(l->{
+ systemTray.remove(getTrayIcon());
+ Platform.runLater(()->{
+ //getStage().setIconified(false);
+ getStage().show();
+ });
+ });
+
+ MenuItem exitItem = new MenuItem("Exit");
+ exitItem.addActionListener(l->{
+ systemTray.remove(getTrayIcon());
+ onQuitApp();
+ Platform.exit();
+ });
+
+ popup.add(showItem);
+ popup.addSeparator();
+ popup.add(exitItem);
+
+ TrayIcon trayIcon = new TrayIcon(
+ Toolkit.getDefaultToolkit().getImage(Main.class.getResource("app_icon.png")),
+ "StreamPi Server",
+ popup
+ );
+
+ trayIcon.setImageAutoSize(true);
+
+ this.trayIcon = trayIcon;
+ }
+
+ private TrayIcon trayIcon = null;
+
+ public TrayIcon getTrayIcon()
+ {
+ return trayIcon;
+ }
+
+ @Override
+ public void handleMinorException(MinorException e) {
+ getLogger().log(Level.SEVERE, e.getMessage());
+ e.printStackTrace();
+
+ Platform.runLater(()-> new StreamPiAlert(e.getTitle(), e.getShortMessage(), StreamPiAlertType.WARNING).show());
+ }
+
+ @Override
+ public void handleSevereException(SevereException e) {
+ getLogger().log(Level.SEVERE, e.getMessage());
+ e.printStackTrace();
+
+ Platform.runLater(()->{
+ StreamPiAlert alert = new StreamPiAlert(e.getTitle(), e.getShortMessage(), StreamPiAlertType.ERROR);
+
+ alert.setOnClicked(new StreamPiAlertListener()
+ {
+ @Override
+ public void onClick(String txt)
+ {
+ onQuitApp();
+ Platform.exit();
+ }
+ });
+
+ alert.show();
+ });
+ }
+
+ @Override
+ public boolean onNormalActionClicked(NormalAction action) {
+ try{
+ getLogger().info("Action "+action.getID()+" clicked!");
+
+ action.onActionClicked();
+ return true;
+ }
+ catch (Exception e)
+ {
+ handleMinorException(new MinorException(
+ "Action Execution Failed!",
+ "Error running Action at ["+action.getLocation().getRow()+","+action.getLocation().getCol()+"] ("+action.getDisplayText()+")\n"+
+ "Check stacktrace/log to know what exactly happened\n\nMessage : \n"+e.getMessage() )
+ );
+ return false;
+ }
+ }
+
+ @Override
+ public void clearTemp() {
+ Platform.runLater(() -> {
+ getDashboardPane().getClientDetailsPane().refresh();
+ getDashboardPane().getActionGridPane().clear();
+ getDashboardPane().getActionDetailsPane().clear();
+ getSettingsPane().getClientsSettings().loadData();
+ });
+ }
+
+ @Override
+ public void saveServerProperties() {
+ try {
+ NormalActionPlugins.getInstance().saveServerSettings();
+ getSettingsPane().getPluginsSettings().loadPlugins();
+ } catch (MinorException e) {
+ e.printStackTrace();
+ handleMinorException(e);
+ }
+ }
+
+ @Override
+ public com.StreamPi.Util.Platform.Platform getPlatform() {
+ return ServerInfo.getInstance().getPlatformType();
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/IO/Config.java
@@ -0,0 +1,324 @@
+/*
+Config.java
+
+Contributor(s) : Debayan Sutradhar (@rnayabed)
+
+handler for config.xml
+ */
+
+package com.StreamPi.Server.IO;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import com.StreamPi.Server.Info.ServerInfo;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.XMLConfigHelper.XMLConfigHelper;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class Config {
+
+ private static Config instance = null;
+
+ private final File configFile;
+
+ private Document document;
+
+ private Config() throws SevereException {
+ try {
+ configFile = new File(ServerInfo.getInstance().getPrePath()+"config.xml");
+ DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
+ document = docBuilder.parse(configFile);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new SevereException("Config", "unable to read config.xml");
+ }
+ }
+
+ public static synchronized Config getInstance() throws SevereException
+ {
+ if(instance == null)
+ instance = new Config();
+
+ return instance;
+ }
+
+ Logger logger = Logger.getLogger(Config.class.getName());
+
+ public void save() throws SevereException {
+ try {
+ logger.info("Saving config ...");
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ Result output = new StreamResult(configFile);
+ Source input = new DOMSource(document);
+
+ transformer.transform(input, output);
+ logger.info("... Done!");
+ } catch (Exception e) {
+ throw new SevereException("Config", "unable to save config.xml");
+ }
+ }
+
+
+ //Getters
+
+ //comms
+ private Element getCommsElement()
+ {
+ return (Element) document.getElementsByTagName("comms").item(0);
+ }
+
+ public String getServerName()
+ {
+ return XMLConfigHelper.getStringProperty(getCommsElement(), "name",
+ getDefaultServerName(), false, true, document, configFile);
+ }
+
+ public int getPort()
+ {
+ return XMLConfigHelper.getIntProperty(getCommsElement(), "port",
+ getDefaultPort(), false, true, document, configFile);
+ }
+
+ //default getters
+ public String getDefaultServerName()
+ {
+ return "StreamPi Server";
+ }
+
+ public int getDefaultPort()
+ {
+ return 2004;
+ }
+
+ private Element getServerElement()
+ {
+ return (Element) document.getElementsByTagName("server").item(0);
+ }
+
+
+ //server
+
+ private Element getActionGridElement()
+ {
+ return (Element) getServerElement().getElementsByTagName("action-grid").item(0);
+ }
+ public int getActionGridActionGap()
+ {
+ return XMLConfigHelper.getIntProperty(getActionGridElement(), "gap",
+ getDefaultActionGridActionGap(), false, true, document, configFile);
+ }
+
+ public int getActionGridActionSize()
+ {
+ return XMLConfigHelper.getIntProperty(getActionGridElement(), "size",
+ getDefaultActionGridSize(), false, true, document, configFile);
+ }
+
+
+ public String getCurrentThemeFullName()
+ {
+ return XMLConfigHelper.getStringProperty(getServerElement(), "current-theme-full-name",
+ getDefaultCurrentThemeFullName(), false, true, document, configFile);
+ }
+
+ public String getThemesPath()
+ {
+ return XMLConfigHelper.getStringProperty(getServerElement(), "themes-path",
+ getDefaultThemesPath(), false, true, document, configFile);
+ }
+
+
+ public String getPluginsPath()
+ {
+ return XMLConfigHelper.getStringProperty(getServerElement(), "plugins-path",
+ getDefaultPluginsPath(), false, true, document, configFile);
+ }
+
+ //default getters
+ public String getDefaultCurrentThemeFullName()
+ {
+ return "com.StreamPi.DefaultLight";
+ }
+
+ public String getDefaultThemesPath()
+ {
+ return "Themes/";
+ }
+
+ public String getDefaultPluginsPath()
+ {
+ return "Plugins/";
+ }
+
+
+ //server > startup-window-size
+
+ private Element getStartupWindowSizeElement()
+ {
+ return (Element) getServerElement().getElementsByTagName("startup-window-size").item(0);
+ }
+
+ public double getStartupWindowWidth()
+ {
+ return XMLConfigHelper.getDoubleProperty(getStartupWindowSizeElement(), "width",
+ getDefaultStartupWindowWidth(), false, true, document, configFile);
+ }
+
+ public double getStartupWindowHeight()
+ {
+ return XMLConfigHelper.getDoubleProperty(getStartupWindowSizeElement(), "height",
+ getDefaultStartupWindowHeight(), false, true, document, configFile);
+ }
+
+ //default getters
+ public int getDefaultStartupWindowWidth()
+ {
+ return 1024;
+ }
+
+ public int getDefaultStartupWindowHeight()
+ {
+ return 768;
+ }
+
+
+ //others
+ private Element getOthersElement()
+ {
+ return (Element) document.getElementsByTagName("others").item(0);
+ }
+
+ public boolean getStartOnBoot()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "start-on-boot",
+ getDefaultStartOnBoot(), false, true, document, configFile);
+ }
+
+ public boolean getCloseOnX()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "close-on-x",
+ getDefaultCloseOnX(), false, true, document, configFile);
+ }
+
+ public boolean isFirstTimeUse()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "first-time-use", true, false, true, document, configFile);
+ }
+
+ public boolean isAllowDonatePopup()
+ {
+ return XMLConfigHelper.getBooleanProperty(getOthersElement(), "allow-donate-popup", true, false, true, document, configFile);
+ }
+
+ //default getters
+ public boolean getDefaultStartOnBoot()
+ {
+ return false;
+ }
+
+ public boolean getDefaultCloseOnX()
+ {
+ return false;
+ }
+
+
+ //Setters
+
+ //comms
+ public void setServerName(String name)
+ {
+ getCommsElement().getElementsByTagName("name").item(0).setTextContent(name);
+ }
+
+ public void setServerPort(int port)
+ {
+ getCommsElement().getElementsByTagName("port").item(0).setTextContent(port+"");
+ }
+
+ //server
+
+ public int getDefaultActionGridActionGap()
+ {
+ return 5;
+ }
+
+ public int getDefaultActionGridSize()
+ {
+ return 100;
+ }
+
+ public void setActionGridSize(int size)
+ {
+ getActionGridElement().getElementsByTagName("size").item(0).setTextContent(size+"");
+ }
+
+ public void setActionGridGap(int size)
+ {
+ getActionGridElement().getElementsByTagName("gap").item(0).setTextContent(size+"");
+ }
+
+ public void setPluginsPath(String path)
+ {
+ getServerElement().getElementsByTagName("plugins-path").item(0).setTextContent(path);
+ }
+
+ public void setThemesPath(String path)
+ {
+ getServerElement().getElementsByTagName("themes-path").item(0).setTextContent(path);
+ }
+
+ public void setCurrentThemeFullName(String themeName)
+ {
+ getServerElement().getElementsByTagName("current-theme-full-name").item(0).setTextContent(themeName);
+ }
+
+ //server > startup-window-size
+ public void setStartupWindowSize(double width, double height)
+ {
+ setStartupWindowWidth(width);
+ setStartupWindowHeight(height);
+ }
+
+ public void setStartupWindowWidth(double width)
+ {
+ getStartupWindowSizeElement().getElementsByTagName("width").item(0).setTextContent(width+"");
+ }
+
+ public void setStartupWindowHeight(double height)
+ {
+ getStartupWindowSizeElement().getElementsByTagName("height").item(0).setTextContent(height+"");
+ }
+
+ //others
+ public void setStartupOnBoot(boolean value)
+ {
+ getOthersElement().getElementsByTagName("start-on-boot").item(0).setTextContent(value+"");
+ }
+
+ public void setCloseOnX(boolean value)
+ {
+ getOthersElement().getElementsByTagName("close-on-x").item(0).setTextContent(value+"");
+ }
+
+ public void setFirstTimeUse(boolean value)
+ {
+ getOthersElement().getElementsByTagName("first-time-use").item(0).setTextContent(value+"");
+ }
+
+ public void setAllowDonatePopup(boolean value)
+ {
+ getOthersElement().getElementsByTagName("allow-donate-popup").item(0).setTextContent(value+"");
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Info/License.java
@@ -0,0 +1,19 @@
+package com.StreamPi.Server.Info;
+
+public class License {
+ public static String getLicense()
+ {
+ return "StreamPi - Free & Opensource Modular Cross-Platform Programmable Macropad\n" +
+ "Copyright (C) 2020 StreamPi Team\n" +
+ "\n" +
+ "This program is free software: you can redistribute it and/or modify\n" +
+ "it under the terms of the GNU General Public License as published by\n" +
+ "the Free Software Foundation, either version 3 of the License, or\n" +
+ "(at your option) any later version.\n" +
+ "\n" +
+ "This program is distributed in the hope that it will be useful,\n" +
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" +
+ "GNU General Public License for more details.\n";
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Info/ServerInfo.java
@@ -0,0 +1,123 @@
+/*
+ServerInfo.java
+
+Stores basic information about the server - name, platform type
+
+Contributors: Debayan Sutradhar (@dubbadhar)
+ */
+
+package com.StreamPi.Server.Info;
+
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import com.StreamPi.Util.Version.Version;
+
+public class ServerInfo {
+ private Version version;
+ private final ReleaseStatus releaseStatus;
+ private final Platform platformType;
+
+ private String prePath;
+
+ private Version minThemeSupportVersion;
+ private Version minPluginSupportVersion;
+ private Version commAPIVersion;
+
+ private static ServerInfo instance = null;
+
+ private String runnerFileName = null;
+ private boolean startMinimised = false;
+
+ private ServerInfo(){
+
+ try {
+ version = new Version("1.0.0");
+ minThemeSupportVersion = new Version("1.0.0");
+ minPluginSupportVersion = new Version("1.0.0");
+ commAPIVersion = new Version("1.0.0");
+ } catch (MinorException e) {
+ e.printStackTrace();
+ }
+
+ releaseStatus = ReleaseStatus.EA;
+ prePath = "data/";
+
+ String osName = System.getProperty("os.name").toLowerCase();
+
+ if(osName.contains("windows"))
+ platformType = Platform.WINDOWS;
+ else if (osName.contains("linux"))
+ platformType = Platform.LINUX;
+ else if (osName.contains("mac"))
+ platformType = Platform.MAC;
+ else
+ platformType = Platform.UNKNOWN;
+
+
+ }
+
+
+ public String getPrePath() {
+ return prePath;
+ }
+
+ public void setStartMinimised(boolean startMinimised)
+ {
+ this.startMinimised = startMinimised;
+ }
+
+ public boolean isStartMinimised()
+ {
+ return startMinimised;
+ }
+
+ public void setRunnerFileName(String runnerFileName)
+ {
+ this.runnerFileName = runnerFileName;
+ }
+
+ public String getRunnerFileName()
+ {
+ return runnerFileName;
+ }
+
+ public static synchronized ServerInfo getInstance(){
+ if(instance == null)
+ {
+ instance = new ServerInfo();
+ }
+
+ return instance;
+ }
+
+
+ public Platform getPlatformType()
+ {
+ return platformType;
+ }
+
+ public Version getVersion() {
+ return version;
+ }
+
+ public ReleaseStatus getReleaseStatus()
+ {
+ return releaseStatus;
+ }
+
+ public Version getMinThemeSupportVersion()
+ {
+ return minThemeSupportVersion;
+ }
+
+ public Version getMinPluginSupportVersion()
+ {
+ return minPluginSupportVersion;
+ }
+
+ public Version getCommAPIVersion()
+ {
+ return commAPIVersion;
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Main.java
@@ -0,0 +1,45 @@
+/*
+Main.java
+
+First class started when the app runs.
+
+Written by Debayan Sutradhar (@rnayabed)
+ */
+
+
+package com.StreamPi.Server;
+
+import com.StreamPi.Server.Controller.Controller;
+import com.StreamPi.Server.Info.ServerInfo;
+
+import javafx.application.Application;
+import javafx.application.HostServices;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+import javafx.stage.Stage;
+
+public class Main extends Application {
+ public void start(Stage stage) {
+ Controller d = new Controller(); //Starts new dash instance
+
+ Scene s = new Scene(d); //Starts new scene instance from dash
+ stage.setScene(s); //Init Scene
+ d.setHostServices(getHostServices());
+ d.init();
+ }
+
+ public static void main(String[] args)
+ {
+ for(String eachArg : args)
+ {
+ String[] r = eachArg.split("=");
+ if(r[0].equals("-DStreamPi.startupRunnerFileName"))
+ ServerInfo.getInstance().setRunnerFileName(r[1]);
+ else if(r[0].equals("-DStreamPi.startupMode"))
+ ServerInfo.getInstance().setStartMinimised(r[1].equals("minimise"));
+ }
+
+
+ launch(args);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/UIPropertyBox/UIPropertyBox.java
@@ -0,0 +1,81 @@
+package com.StreamPi.Server.UIPropertyBox;
+
+import com.StreamPi.ActionAPI.ActionProperty.Property.ControlType;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import javafx.scene.Node;
+import javafx.scene.control.ComboBox;
+import javafx.scene.control.Slider;
+import javafx.scene.control.TextField;
+import javafx.scene.control.ToggleButton;
+
+public class UIPropertyBox
+{
+ private Node controlNode;
+ private boolean canBeBlank;
+ private String displayName;
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public boolean isCanBeBlank() {
+ return canBeBlank;
+ }
+
+ private int index;
+
+ private ControlType controlType;
+ private Type type;
+
+ public UIPropertyBox(int index, String displayName, Node controlNode, ControlType controlType, Type type, boolean canBeBlank)
+ {
+ this.index = index;
+ this.displayName = displayName;
+ this.controlNode = controlNode;
+ this.controlType = controlType;
+ this.type = type;
+ this.canBeBlank = canBeBlank;
+ }
+
+ public ControlType getControlType()
+ {
+ return controlType;
+ }
+
+ public Node getControlNode()
+ {
+ return controlNode;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public Type getType()
+ {
+ return type;
+ }
+
+ public String getRawValue()
+ {
+ String rawValue = null;
+
+ if (controlType == ControlType.TEXT_FIELD)
+ rawValue = ((TextField) controlNode).getText();
+ else if (controlType == ControlType.COMBO_BOX)
+ rawValue = ((ComboBox<String>) controlNode).getSelectionModel().getSelectedIndex() + "";
+ else if (controlType == ControlType.SLIDER_DOUBLE)
+ rawValue = ((Slider) controlNode).getValue() + "";
+ else if (controlType == ControlType.SLIDER_INTEGER)
+ rawValue = Math.round(((Slider) controlNode).getValue()) + "";
+ else if (controlType == ControlType.TOGGLE) {
+ ToggleButton toggleButton = ((ToggleButton) controlNode);
+ if (toggleButton.isSelected())
+ rawValue = "true";
+ else
+ rawValue = "false";
+ }
+
+ return rawValue;
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Base.java
@@ -0,0 +1,271 @@
+package com.StreamPi.Server.Window;
+
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.IO.Config;
+import com.StreamPi.Server.Info.ServerInfo;
+import com.StreamPi.Server.Main;
+import com.StreamPi.Server.Window.Dashboard.*;
+import com.StreamPi.Server.Window.Settings.SettingsBase;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.ThemeAPI.Themes;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.LoggerHelper.StreamPiLogFileHandler;
+
+import javafx.application.HostServices;
+import javafx.scene.image.Image;
+import javafx.scene.layout.StackPane;
+import javafx.scene.text.Font;
+import javafx.stage.Stage;
+import java.awt.SystemTray;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.io.IOException;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+import java.awt.PopupMenu;
+import java.awt.MenuItem;
+
+public abstract class Base extends StackPane implements ExceptionAndAlertHandler, ServerListener {
+
+ private Config config;
+
+ private ServerInfo serverInfo;
+
+ private Stage stage;
+
+ private HostServices hostServices;
+
+ public Logger getLogger(){
+ return logger;
+ }
+
+ private SettingsBase settingsBase;
+ private DashboardBase dashboardBase;
+
+ private StackPane alertStackPane;
+
+ public void setHostServices(HostServices hostServices)
+ {
+ this.hostServices = hostServices;
+ }
+
+ public HostServices getHostServices()
+ {
+ return hostServices;
+ }
+
+ private Logger logger = null;
+ private StreamPiLogFileHandler logFileHandler = null;
+
+ public void initLogger() throws SevereException
+ {
+ try
+ {
+ if(logger != null)
+ return;
+
+ logger = Logger.getLogger("");
+ logFileHandler = new StreamPiLogFileHandler(ServerInfo.getInstance().getPrePath()+"../streampi.log");
+ logger.addHandler(logFileHandler);
+ }
+ catch(Exception e)
+ {
+ e.printStackTrace();
+ throw new SevereException("Cant get logger started!");
+ }
+ }
+
+ public void closeLogger()
+ {
+ if(logFileHandler != null)
+ logFileHandler.close();
+ }
+
+ public void initBase() throws SevereException {
+ initLogger();
+
+ getChildren().clear();
+
+ stage = (Stage) getScene().getWindow();
+
+ getStage().getIcons().add(new Image(Main.class.getResourceAsStream("app_icon.png")));
+ config = Config.getInstance();
+
+ stage.setWidth(config.getStartupWindowWidth());
+ stage.setHeight(config.getStartupWindowHeight());
+
+
+ serverInfo = ServerInfo.getInstance();
+
+
+ initThemes();
+
+ settingsBase = new SettingsBase(getHostServices(), this, this);
+ settingsBase.prefWidthProperty().bind(widthProperty());
+ settingsBase.prefHeightProperty().bind(heightProperty());
+
+ dashboardBase = new DashboardBase(this, getHostServices());
+ dashboardBase.prefWidthProperty().bind(widthProperty());
+ dashboardBase.prefHeightProperty().bind(heightProperty());
+
+ alertStackPane = new StackPane();
+ alertStackPane.setVisible(false);
+
+ StreamPiAlert.setParent(alertStackPane);
+
+ getChildren().addAll(settingsBase, dashboardBase, alertStackPane);
+
+ dashboardBase.toFront();
+
+
+ }
+
+ public void initThemes() throws SevereException {
+ clearStylesheets();
+
+ registerThemes();
+ applyDefaultStylesheet();
+ applyDefaultTheme();
+ }
+
+ public Stage getStage()
+ {
+ return stage;
+ }
+
+ public void applyDefaultStylesheet()
+ {
+ logger.info("Applying default stylesheet ...");
+
+ Font.loadFont(Main.class.getResourceAsStream("Roboto.ttf"), 13);
+ getStylesheets().add(Main.class.getResource("style.css").toExternalForm());
+
+ logger.info("... Done!");
+ }
+
+ public DashboardBase getDashboardPane()
+ {
+ return dashboardBase;
+ }
+
+ public SettingsBase getSettingsPane()
+ {
+ return settingsBase;
+ }
+
+
+ public Config getConfig()
+ {
+ return config;
+ }
+
+ public ServerInfo getServerInfo()
+ {
+ return serverInfo;
+ }
+
+ private Theme currentTheme;
+ public Theme getCurrentTheme()
+ {
+ return currentTheme;
+ }
+
+ public void applyTheme(Theme t)
+ {
+ logger.info("Applying theme '"+t.getFullName()+"' ...");
+
+ if(t.getFonts() != null)
+ {
+ for(String fontFile : t.getFonts())
+ {
+ Font.loadFont(fontFile.replace("%20",""), 13);
+ }
+ }
+
+ currentTheme = t;
+ getStylesheets().addAll(t.getStylesheets());
+
+ logger.info("... Done!");
+ }
+
+ public void clearStylesheets()
+ {
+ getStylesheets().clear();
+ }
+
+ Themes themes;
+ public void registerThemes() throws SevereException
+ {
+ logger.info("Loading themes ...");
+ themes = new Themes(getConfig().getThemesPath(), getConfig().getCurrentThemeFullName(), serverInfo.getMinThemeSupportVersion());
+
+ if(themes.getErrors().size()>0)
+ {
+ StringBuilder themeErrors = new StringBuilder();
+
+ for(MinorException eachException : themes.getErrors())
+ {
+ themeErrors.append("\n * ").append(eachException.getShortMessage());
+ }
+
+ if(themes.getIsBadThemeTheCurrentOne())
+ {
+ themeErrors.append("\n\nReverted to default theme! (").append(getConfig().getDefaultCurrentThemeFullName()).append(")");
+
+ getConfig().setCurrentThemeFullName(getConfig().getDefaultCurrentThemeFullName());
+ getConfig().save();
+ }
+
+ handleMinorException(new MinorException("Theme Loading issues", themeErrors.toString()));
+ }
+
+
+ logger.info("... Done!");
+ }
+
+ public Themes getThemes()
+ {
+ return themes;
+ }
+
+ public void applyDefaultTheme()
+ {
+ logger.info("Applying default theme ...");
+
+
+
+ boolean foundTheme = false;
+ for(Theme t: themes.getThemeList())
+ {
+ if(t.getFullName().equals(config.getCurrentThemeFullName()))
+ {
+ foundTheme = true;
+ applyTheme(t);
+ break;
+ }
+ }
+
+ if(foundTheme)
+ logger.info("... Done!");
+ else
+ {
+ logger.info("Theme not found. reverting to light theme ...");
+ try {
+ Config.getInstance().setCurrentThemeFullName("com.StreamPi.DefaultLight");
+ Config.getInstance().save();
+
+ applyDefaultTheme();
+ }
+ catch (SevereException e)
+ {
+ handleSevereException(e);
+ }
+ }
+
+
+ }
+
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionGridPane/ActionBox.java
@@ -0,0 +1,367 @@
+package com.StreamPi.Server.Window.Dashboard.ActionGridPane;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.OtherActions.CombineAction;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Server.Window.Dashboard.ActionsDetailPane.ActionDetailsPaneListener;
+import com.StreamPi.Util.Exception.MinorException;
+import javafx.application.Platform;
+import javafx.css.CssMetaData;
+import javafx.css.Styleable;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.SnapshotParameters;
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuItem;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.image.WritableImage;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.input.Dragboard;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.TransferMode;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.BackgroundImage;
+import javafx.scene.layout.BackgroundPosition;
+import javafx.scene.layout.BackgroundRepeat;
+import javafx.scene.layout.BackgroundSize;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.ImagePattern;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.TextAlignment;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.nio.ByteBuffer;
+import java.util.function.Consumer;
+
+public class ActionBox extends StackPane{
+
+ private Label displayTextLabel;
+
+ private int row;
+ private int col;
+
+ public void setRow(int row) {
+ this.row = row;
+ }
+
+ public void setCol(int col) {
+ this.col = col;
+ }
+
+ public int getRow() {
+ return row;
+ }
+
+ public int getCol() {
+ return col;
+ }
+
+ private ActionDetailsPaneListener actionDetailsPaneListener;
+
+ public void clear()
+ {
+ setAction(null);
+ setInvalid(false);
+ setBackground(Background.EMPTY);
+ setStyle(null);
+ getChildren().clear();
+ }
+
+ public void baseInit()
+ {
+
+ displayTextLabel = new Label();
+ displayTextLabel.setWrapText(true);
+ displayTextLabel.setTextAlignment(TextAlignment.CENTER);
+ displayTextLabel.getStyleClass().add("action_box_display_text_label");
+
+ displayTextLabel.prefHeightProperty().bind(heightProperty());
+ displayTextLabel.prefWidthProperty().bind(widthProperty());
+
+
+ getChildren().addAll(displayTextLabel);
+
+ setMinSize(size, size);
+ setMaxSize(size, size);
+
+ getStyleClass().add("action_box");
+ getStyleClass().add("action_box_icon_not_present");
+ getStyleClass().add("action_box_valid");
+
+
+ setOnDragOver(dragEvent ->
+ {
+ if(dragEvent.getDragboard().hasContent(Action.getDataFormat()))
+ {
+ dragEvent.acceptTransferModes(TransferMode.ANY);
+
+ dragEvent.consume();
+ }
+ });
+
+ setOnDragDropped(dragEvent ->
+ {
+ try
+ {
+ if(action == null)
+ {
+ Action action = (Action) dragEvent.getDragboard().getContent(Action.getDataFormat());
+
+ action.setLocation(new Location(getRow(),
+ getCol()));
+
+ action.setParent(getStreamPiParent());
+
+ action.setIDRandom();
+
+
+ actionGridPaneListener.addActionToCurrentClientProfile(action);
+
+ setAction(action);
+ init();
+
+
+ actionDetailsPaneListener.onActionClicked(action, this);
+
+ if(action.isHasIcon())
+ actionDetailsPaneListener.setSendIcon(true);
+
+ actionDetailsPaneListener.saveAction();
+ }
+ }
+ catch (MinorException e)
+ {
+ exceptionAndAlertHandler.handleMinorException(e);
+ e.printStackTrace();
+ }
+ });
+
+ setOnDragDetected(mouseEvent -> {
+ try {
+ if(action!=null)
+ {
+ if(action.getActionType() == ActionType.NORMAL)
+ {
+ Dragboard db = startDragAndDrop(TransferMode.ANY);
+
+ ClipboardContent content = new ClipboardContent();
+
+ Action newAction = (Action) action.clone();
+
+ newAction.setIDRandom();
+ newAction.setParent(getStreamPiParent());
+
+ content.put(Action.getDataFormat(), newAction);
+
+ db.setContent(content);
+
+ mouseEvent.consume();
+ }
+ }
+ }
+ catch (CloneNotSupportedException e)
+ {
+ e.printStackTrace();
+ }
+ });
+
+ setOnMouseClicked(mouseEvent -> {
+ if(action != null && mouseEvent.getButton().equals(MouseButton.PRIMARY))
+ {
+ if(mouseEvent.getClickCount() == 2 && action.getActionType() == ActionType.FOLDER)
+ {
+ getActionDetailsPaneListener().onOpenFolderButtonClicked();
+ }
+ else
+ {
+ try
+ {
+ actionDetailsPaneListener.onActionClicked(action, this);
+ }
+ catch (MinorException e)
+ {
+ exceptionAndAlertHandler.handleMinorException(e);
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+
+ setCache(true);
+ setCacheHint(CacheHint.SPEED);
+ }
+
+ public void setInvalid(boolean invalid)
+ {
+ if(invalid)
+ {
+ getStyleClass().remove("action_box_valid");
+ getStyleClass().add("action_box_invalid");
+ }
+ else
+ {
+ getStyleClass().remove("action_box_invalid");
+ getStyleClass().add("action_box_valid");
+ }
+
+ }
+
+ public ActionDetailsPaneListener getActionDetailsPaneListener() {
+ return actionDetailsPaneListener;
+ }
+
+
+ public ActionGridPaneListener getActionGridPaneListener() {
+ return actionGridPaneListener;
+ }
+
+ private int size;
+ private ActionGridPaneListener actionGridPaneListener;
+ public ActionBox(int size, ActionDetailsPaneListener actionDetailsPaneListener, ActionGridPaneListener actionGridPaneListener)
+ {
+ this.actionGridPaneListener = actionGridPaneListener;
+ this.actionDetailsPaneListener = actionDetailsPaneListener;
+ this.size = size;
+ baseInit();
+ }
+
+ public static Action deserialize(ByteBuffer buffer) {
+ try {
+ ByteArrayInputStream is = new ByteArrayInputStream(buffer.array());
+ ObjectInputStream ois = new ObjectInputStream(is);
+ Action obj = (Action) ois.readObject();
+ return obj;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void setIcon(byte[] iconByteArray)
+ {
+ if(iconByteArray == null)
+ {
+ getStyleClass().remove("action_box_icon_present");
+ getStyleClass().add("action_box_icon_not_present");
+ setBackground(null);
+ return;
+ }
+ else
+ {
+ getStyleClass().add("action_box_icon_present");
+ getStyleClass().remove("action_box_icon_not_present");
+
+
+ setBackground(
+ new Background(
+ new BackgroundImage(new Image(
+ new ByteArrayInputStream(iconByteArray), size, size, false, true
+ ), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER,
+
+ new BackgroundSize(100, 100, true, true, true, false))
+ )
+ );
+ }
+ }
+
+ private Action action;
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private String parent;
+
+ public String getStreamPiParent() {
+ return parent;
+ }
+
+ public void setStreamPiParent(String parent) {
+ this.parent = parent;
+ }
+
+ public ActionBox(int size, Action action, ActionDetailsPaneListener actionDetailsPaneListener, ExceptionAndAlertHandler exceptionAndAlertHandler, ActionGridPaneListener actionGridPaneListener)
+ {
+ this.actionGridPaneListener = actionGridPaneListener;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.action = action;
+ this.actionDetailsPaneListener = actionDetailsPaneListener;
+ this.size = size;
+
+
+
+ baseInit();
+
+ init();
+
+ }
+
+ public void setAction(Action action)
+ {
+ this.action = action;
+ }
+
+ public void init()
+ {
+ if(action.isShowDisplayText())
+ setDisplayTextLabel(action.getDisplayText());
+ else
+ setDisplayTextLabel("");
+
+ setDisplayTextAlignment(action.getDisplayTextAlignment());
+ setBackgroundColour(action.getBgColourHex());
+ setDisplayTextFontColour(action.getDisplayTextFontColourHex());
+
+ setInvalid(action.isInvalid());
+
+ Platform.runLater(()->{
+ if(action.isHasIcon() && action.isShowIcon())
+ {
+ setIcon(action.getIconAsByteArray());
+ }
+ else
+ {
+ setIcon(null);
+ }
+ });
+ }
+
+ public void setDisplayTextLabel(String text)
+ {
+ displayTextLabel.setText(text);
+ }
+
+ public void setDisplayTextAlignment(DisplayTextAlignment displayTextAlignment)
+ {
+ if(displayTextAlignment == DisplayTextAlignment.CENTER)
+ displayTextLabel.setAlignment(Pos.CENTER);
+ else if (displayTextAlignment == DisplayTextAlignment.BOTTOM)
+ displayTextLabel.setAlignment(Pos.BOTTOM_CENTER);
+ else if (displayTextAlignment == DisplayTextAlignment.TOP)
+ displayTextLabel.setAlignment(Pos.TOP_CENTER);
+ }
+
+ public void setDisplayTextFontColour(String colour)
+ {
+ System.out.println("COLOr : "+colour);
+ if(!colour.isEmpty())
+ displayTextLabel.setStyle("-fx-text-fill : "+colour+";");
+ }
+
+ public void setBackgroundColour(String colour)
+ {
+ System.out.println("COLOr : "+colour);
+ if(!colour.isEmpty() && action.getIconAsByteArray() == null)
+ setStyle("-fx-background-color : "+colour);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionGridPane/ActionGridPane.java
@@ -0,0 +1,303 @@
+package com.StreamPi.Server.Window.Dashboard.ActionGridPane;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.IO.Config;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Server.Window.Dashboard.ActionsDetailPane.ActionDetailsPaneListener;
+import com.StreamPi.ThemeAPI.Themes;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import javafx.event.EventHandler;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.Node;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.*;
+import javafx.scene.paint.Paint;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+import java.util.ArrayList;
+import java.util.logging.Logger;
+
+public class ActionGridPane extends ScrollPane implements ActionGridPaneListener {
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+ private ActionDetailsPaneListener actionDetailsPaneListener;
+
+ public ActionGridPane(ExceptionAndAlertHandler exceptionAndAlertHandler)
+ {
+ logger = Logger.getLogger(ActionGridPane.class.getName());
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ getStyleClass().add("action_grid_pane_scroll_pane");
+
+ VBox.setVgrow(this, Priority.ALWAYS);
+
+ actionsGridPane = new GridPane();
+ actionsGridPane.setPadding(new Insets(5.0));
+ actionsGridPane.getStyleClass().add("action_grid_pane");
+
+ actionsGridPane.setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE);
+
+ setContent(actionsGridPane);
+ }
+
+ public void setActionDetailsPaneListener(ActionDetailsPaneListener actionDetailsPaneListener) {
+ this.actionDetailsPaneListener = actionDetailsPaneListener;
+ }
+
+ private String currentParent;
+
+ public void setCurrentParent(String currentParent) {
+ this.currentParent = currentParent;
+ }
+
+ public ClientProfile getClientProfile() {
+ return clientProfile;
+ }
+
+ private Client client;
+
+ public void setClient(Client client) {
+ this.client = client;
+ }
+
+ public Client getClient() {
+ return client;
+ }
+
+ private int rows, cols;
+
+ private GridPane actionsGridPane;
+
+ private ClientProfile clientProfile;
+
+ public void setClientProfile(ClientProfile clientProfile)
+ {
+ this.clientProfile = clientProfile;
+
+ setCurrentParent("root");
+ setRows(clientProfile.getRows());
+ setCols(clientProfile.getCols());
+ }
+
+
+ public String getCurrentParent() {
+ return currentParent;
+ }
+
+ public StackPane getFolderBackButton() throws SevereException
+ {
+ StackPane stackPane = new StackPane();
+ stackPane.getStyleClass().add("action_box");
+ stackPane.getStyleClass().add("action_box_valid");
+
+ stackPane.setPrefSize(
+ Config.getInstance().getActionGridActionSize(),
+ Config.getInstance().getActionGridActionSize()
+ );
+
+ FontIcon fontIcon = new FontIcon("fas-chevron-left");
+
+ fontIcon.setIconSize(Config.getInstance().getActionGridActionSize() - 30);
+
+ stackPane.setAlignment(Pos.CENTER);
+ stackPane.getChildren().add(fontIcon);
+
+ stackPane.setOnMouseClicked(e->returnToPreviousParent());
+
+ return stackPane;
+ }
+
+ public void renderGrid() throws SevereException {
+ clear();
+
+ actionsGridPane.setHgap(Config.getInstance().getActionGridActionGap());
+ actionsGridPane.setVgap(Config.getInstance().getActionGridActionGap());
+
+ boolean isFolder = false;
+
+ if(!getCurrentParent().equals("root"))
+ {
+ isFolder = true;
+
+ actionsGridPane.add(getFolderBackButton(), 0,0);
+ }
+
+ for(int row = 0; row<rows; row++)
+ {
+ for(int col = 0; col<cols; col++)
+ {
+ if(row == 0 && col == 0 && isFolder)
+ continue;
+
+ ActionBox actionBox = new ActionBox(Config.getInstance().getActionGridActionSize(), actionDetailsPaneListener, this);
+
+ actionBox.setStreamPiParent(currentParent);
+ actionBox.setRow(row);
+ actionBox.setCol(col);
+
+ actionsGridPane.add(actionBox, row, col);
+
+ }
+ }
+ }
+
+ public void renderActions()
+ {
+ StringBuilder errors = new StringBuilder();
+ for(String action1x : getClientProfile().getActionsKeySet())
+ {
+ Action eachAction = getClientProfile().getActionByID(action1x);
+ logger.info("Action ID : "+eachAction.getID()+
+ "\nInvalid : "+eachAction.isInvalid());
+
+ try {
+ renderAction(eachAction);
+ }
+ catch (SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ catch (MinorException e)
+ {
+ errors.append("*").append(e.getShortMessage()).append("\n");
+ }
+ }
+
+ if(!errors.toString().isEmpty())
+ {
+ exceptionAndAlertHandler.handleMinorException(new MinorException("Error while rendering following actions", errors.toString()));
+ }
+ }
+
+ public void clear()
+ {
+ actionsGridPane.getChildren().clear();
+ }
+
+ private Logger logger;
+
+ public void renderAction(Action action) throws SevereException, MinorException
+ {
+ if(!action.getParent().equals(currentParent))
+ {
+ logger.info("Skipping action "+action.getID()+", not current parent!");
+ return;
+ }
+
+ if(action.getLocation().getRow()==-1)
+ {
+ logger.info("Action has -1 rowIndex. Probably Combine Action. Skipping ...");
+ return;
+ }
+
+ if(action.getLocation().getRow() >= rows || action.getLocation().getCol() >= cols)
+ {
+ throw new MinorException("Action "+action.getDisplayText()+" ("+action.getID()+") falls outside bounds.\n" +
+ " Consider increasing rows/cols from client settings and relocating/deleting it.");
+ }
+
+
+ ActionBox actionBox = new ActionBox(Config.getInstance().getActionGridActionSize(), action, actionDetailsPaneListener, exceptionAndAlertHandler, this);
+
+ Location location = action.getLocation();
+
+ actionBox.setStreamPiParent(currentParent);
+ actionBox.setRow(location.getRow());
+ actionBox.setCol(location.getCol());
+
+ for(Node node : actionsGridPane.getChildren())
+ {
+ if(GridPane.getColumnIndex(node) == location.getRow() &&
+ GridPane.getRowIndex(node) == location.getCol())
+ {
+ actionsGridPane.getChildren().remove(node);
+ break;
+ }
+ }
+
+ System.out.println(location.getCol()+","+location.getRow());
+ actionsGridPane.add(actionBox, location.getRow(), location.getCol());
+
+ }
+
+ public void setRows(int rows)
+ {
+ this.rows = rows;
+ }
+
+ public void setCols(int cols)
+ {
+ this.cols = cols;
+ }
+
+ public int getRows()
+ {
+ return rows;
+ }
+
+ public int getCols()
+ {
+ return cols;
+ }
+
+
+ @Override
+ public void addActionToCurrentClientProfile(Action newAction) {
+ try {
+ getClientProfile().addAction(newAction);
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private String previousParent;
+
+ public void setPreviousParent(String previousParent) {
+ this.previousParent = previousParent;
+ }
+
+ public String getPreviousParent() {
+ return previousParent;
+ }
+
+ @Override
+ public void renderFolder(FolderAction action) {
+ setCurrentParent(action.getID());
+ setPreviousParent(action.getParent());
+ try {
+ renderGrid();
+ renderActions();
+ } catch (SevereException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void returnToPreviousParent()
+ {
+ setCurrentParent(getPreviousParent());
+
+ if(!getPreviousParent().equals("root"))
+ {
+ System.out.println("parent : "+getPreviousParent());
+ setPreviousParent(getClientProfile().getActionByID(
+ getPreviousParent()
+ ).getParent());
+ }
+
+ try {
+ renderGrid();
+ renderActions();
+ } catch (SevereException e) {
+ e.printStackTrace();
+ }
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionGridPane/ActionGridPaneListener.java
@@ -0,0 +1,10 @@
+package com.StreamPi.Server.Window.Dashboard.ActionGridPane;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+
+public interface ActionGridPaneListener {
+ void addActionToCurrentClientProfile(Action newAction);
+
+ void renderFolder(FolderAction action);
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionsDetailPane/ActionDetailsPane.java
@@ -0,0 +1,804 @@
+package com.StreamPi.Server.Window.Dashboard.ActionsDetailPane;
+
+import com.StreamPi.Server.UIPropertyBox.UIPropertyBox;
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.ActionProperty.Property.ControlType;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.ActionAPI.OtherActions.CombineAction;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.Server.Connection.ClientConnections;
+import com.StreamPi.Server.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.FormHelper.HBoxInputBox;
+import com.StreamPi.Util.FormHelper.HBoxInputBoxWithFileChooser;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import javafx.application.HostServices;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.concurrent.Task;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+import javafx.scene.input.TransferMode;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.stage.FileChooser;
+import javafx.stage.Window;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class ActionDetailsPane extends VBox implements ActionDetailsPaneListener {
+
+ private ScrollPane scrollPane;
+
+ private VBox vbox;
+ private VBox clientPropertiesVBox;
+
+ private Button saveButton;
+ private Button deleteButton;
+ private Button openFolderButton;
+
+ private HBox buttonBar;
+
+ private Label actionHeadingLabel;
+
+ private Logger logger;
+
+ private Button returnButtonForCombineActionChild;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private HostServices hostServices;
+
+ public ActionDetailsPane(ExceptionAndAlertHandler exceptionAndAlertHandler, HostServices hostServices) {
+ this.hostServices = hostServices;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+
+ logger = Logger.getLogger(ActionDetailsPane.class.getName());
+
+ setSpacing(10.0);
+
+ // VBox.setVgrow(this, Priority.SOMETIMES);
+
+ clientPropertiesVBox = new VBox();
+ clientPropertiesVBox.setSpacing(10.0);
+
+ vbox = new VBox();
+ vbox.setPadding(new Insets(0, 25, 0, 5));
+ vbox.getStyleClass().add("action_details_pane_vbox");
+
+ vbox.setSpacing(10.0);
+
+ getStyleClass().add("action_details_pane");
+ // setPadding(new Insets(5,50,5,50));
+ // setMinHeight(245);
+
+ scrollPane = new ScrollPane();
+ VBox.setMargin(scrollPane, new Insets(0, 0, 0, 10));
+
+ scrollPane.getStyleClass().add("action_details_pane_scroll_pane");
+
+ setMinHeight(310);
+ scrollPane.setContent(vbox);
+
+ vbox.prefWidthProperty().bind(scrollPane.widthProperty());
+ scrollPane.prefWidthProperty().bind(widthProperty());
+
+ VBox.setVgrow(scrollPane, Priority.ALWAYS);
+
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+
+ openFolderButton = new Button("Open Folder");
+ FontIcon folderOpenIcon = new FontIcon("far-folder-open");
+ openFolderButton.setGraphic(folderOpenIcon);
+ openFolderButton.setOnAction(event -> onOpenFolderButtonClicked());
+
+ saveButton = new Button("Apply Changes");
+ FontIcon syncIcon = new FontIcon("fas-sync-alt");
+ saveButton.setGraphic(syncIcon);
+ saveButton.setOnAction(event -> onSaveButtonClicked());
+
+ deleteButton = new Button("Delete Action");
+ FontIcon deleteIcon = new FontIcon("fas-trash");
+ deleteIcon.setIconColor(Paint.valueOf("#FF0000"));
+ deleteButton.setTextFill(Paint.valueOf("#FF0000"));
+ deleteButton.setGraphic(deleteIcon);
+
+ deleteButton.setOnAction(event -> onDeleteButtonClicked());
+
+ returnButtonForCombineActionChild = new Button("Return");
+ returnButtonForCombineActionChild.setGraphic(new FontIcon("fas-caret-left"));
+ returnButtonForCombineActionChild.managedProperty().bind(returnButtonForCombineActionChild.visibleProperty());
+ returnButtonForCombineActionChild.setOnAction(event -> {
+ try {
+ logger.info("@@## : " + action.getParent());
+ onActionClicked(getClientProfile().getActionByID(action.getParent()), getActionBox());
+ } catch (MinorException e) {
+ e.printStackTrace();
+ }
+ });
+
+ buttonBar = new HBox(openFolderButton, returnButtonForCombineActionChild, saveButton, deleteButton);
+ buttonBar.setPadding(new Insets(10, 10, 10, 0));
+ buttonBar.setAlignment(Pos.CENTER_RIGHT);
+ buttonBar.setVisible(false);
+ buttonBar.setSpacing(10.0);
+
+ actionHeadingLabel = new Label();
+
+ actionHeadingLabel.getStyleClass().add("action_details_pane_action_heading_label");
+
+ HBox headingHBox = new HBox(actionHeadingLabel);
+
+ headingHBox.setPadding(new Insets(5, 10, 0, 10));
+
+ getChildren().addAll(headingHBox, scrollPane, buttonBar);
+
+ displayTextAlignmentComboBox = new ComboBox<>(FXCollections.observableArrayList(DisplayTextAlignment.TOP,
+ DisplayTextAlignment.CENTER, DisplayTextAlignment.BOTTOM));
+
+ displayTextAlignmentComboBox.managedProperty().bind(displayTextAlignmentComboBox.visibleProperty());
+
+ actionClientProperties = new ArrayList<>();
+
+ displayNameTextField = new TextField();
+ displayNameTextField.managedProperty().bind(displayNameTextField.visibleProperty());
+
+ iconFileTextField = new TextField();
+ iconFileTextField.managedProperty().bind(iconFileTextField.visibleProperty());
+ iconFileTextField.textProperty().addListener((observableValue, s, t1) -> {
+ try {
+ if (!s.equals(t1) && t1.length() > 0) {
+ byte[] iconFileByteArray = Files.readAllBytes(new File(t1).toPath());
+
+ hideIconCheckBox.setDisable(false);
+ hideIconCheckBox.setSelected(false);
+ clearIconButton.setDisable(false);
+
+ System.out.println("ABABABABABBABABBABABABCCCCCCCCCCCCCCCCCC");
+
+ action.setIcon(iconFileByteArray);
+
+ System.out.println(action.getIconAsByteArray().length);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(new MinorException(e.getMessage()));
+ }
+ });
+
+ clearIconButton = new Button("Clear Icon");
+ clearIconButton.managedProperty().bind(clearIconButton.visibleProperty());
+ clearIconButton.setOnAction(event -> {
+
+ hideIconCheckBox.setDisable(true);
+ hideIconCheckBox.setSelected(false);
+
+ clearIconButton.setDisable(true);
+ iconFileTextField.clear();
+ });
+
+ hideDisplayTextCheckBox = new CheckBox("Hide");
+ hideDisplayTextCheckBox.managedProperty().bind(hideDisplayTextCheckBox.visibleProperty());
+
+ hideIconCheckBox = new CheckBox("Hide");
+ hideIconCheckBox.managedProperty().bind(hideIconCheckBox.visibleProperty());
+
+ actionBackgroundColourPicker = new ColorPicker();
+ actionBackgroundColourPicker.managedProperty().bind(actionBackgroundColourPicker.visibleProperty());
+
+ displayTextColourPicker = new ColorPicker();
+ displayTextColourPicker.managedProperty().bind(displayTextColourPicker.visibleProperty());
+
+ actionBackgroundColourTransparentCheckBox = new CheckBox("Default");
+ actionBackgroundColourPicker.disableProperty()
+ .bind(actionBackgroundColourTransparentCheckBox.selectedProperty());
+
+ HBox.setMargin(actionBackgroundColourTransparentCheckBox, new Insets(0, 0, 0, 10));
+
+
+ displayTextColourDefaultCheckBox = new CheckBox("Default");
+ displayTextColourPicker.disableProperty()
+ .bind(displayTextColourDefaultCheckBox.selectedProperty());
+
+ HBox.setMargin(displayTextColourDefaultCheckBox, new Insets(0, 0, 0, 10));
+
+ Region r = new Region();
+ HBox.setHgrow(r, Priority.ALWAYS);
+
+ Region r1 = new Region();
+ HBox.setHgrow(r1, Priority.ALWAYS);
+
+ HBox displayTextColourHBox = new HBox(new Label("Display Text Colour"), r1, displayTextColourPicker,
+ displayTextColourDefaultCheckBox);
+ displayTextColourHBox.setAlignment(Pos.CENTER);
+ displayTextColourHBox.setSpacing(5.0);
+
+ HBox bgColourHBox = new HBox(new Label("Background Colour"), r, actionBackgroundColourPicker,
+ actionBackgroundColourTransparentCheckBox);
+ bgColourHBox.setAlignment(Pos.CENTER);
+ bgColourHBox.setSpacing(5.0);
+
+ HBox clearIconHBox = new HBox(clearIconButton);
+ clearIconHBox.setAlignment(Pos.CENTER_RIGHT);
+
+ displayTextFieldHBox = new HBoxInputBox("Display Name", displayNameTextField, hideDisplayTextCheckBox);
+
+ normalActionsPropsVBox = new VBox(displayTextColourHBox,
+
+ new HBox(new Label("Alignment"), new SpaceFiller(SpaceFiller.FillerType.HBox),
+ displayTextAlignmentComboBox),
+
+ new HBoxInputBoxWithFileChooser("Icon", iconFileTextField, hideIconCheckBox,
+ new FileChooser.ExtensionFilter("Images", "*.jpeg", "*.jpg", "*.png", "*.gif")),
+
+ clearIconHBox, bgColourHBox);
+ normalActionsPropsVBox.managedProperty().bind(normalActionsPropsVBox.visibleProperty());
+ normalActionsPropsVBox.setSpacing(10.0);
+
+ vbox.getChildren().addAll(displayTextFieldHBox, normalActionsPropsVBox, clientPropertiesVBox);
+
+ vbox.setVisible(false);
+ scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+
+ setOnDragOver(dragEvent -> {
+ if (dragEvent.getDragboard().hasContent(Action.getDataFormat()) && action != null) {
+ if (action.getActionType() == ActionType.COMBINE) {
+ dragEvent.acceptTransferModes(TransferMode.ANY);
+
+ dragEvent.consume();
+ }
+ }
+ });
+
+ setOnDragDropped(dragEvent -> {
+ try {
+ Action newAction = (Action) dragEvent.getDragboard().getContent(Action.getDataFormat());
+
+ if (newAction.getActionType() == ActionType.NORMAL) {
+ newAction.setLocation(new Location(-1, -1));
+
+ newAction.setParent(this.action.getID());
+
+ combineActionPropertiesPane.getCombineAction().addChild(newAction.getID());
+
+ addActionToCurrentClientProfile(newAction);
+
+ ClientConnection connection = ClientConnections.getInstance()
+ .getClientConnectionBySocketAddress(getClient().getRemoteSocketAddress());
+
+ connection.saveActionDetails(getClientProfile().getID(), newAction);
+
+ combineActionPropertiesPane.renderProps();
+
+ saveAction();
+ }
+ } catch (MinorException e) {
+ exceptionAndAlertHandler.handleMinorException(e);
+ e.printStackTrace();
+ } catch (SevereException e) {
+ exceptionAndAlertHandler.handleSevereException(e);
+ e.printStackTrace();
+ } catch (CloneNotSupportedException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+ private VBox normalActionsPropsVBox;
+
+ private HBox displayTextFieldHBox;
+
+ private Client client;
+ private ClientProfile clientProfile;
+
+ public void setClient(Client client) {
+ this.client = client;
+ }
+
+ public Client getClient() {
+ return client;
+ }
+
+ public void setClientProfile(ClientProfile clientProfile) {
+ this.clientProfile = clientProfile;
+ }
+
+ public ClientProfile getClientProfile() {
+ return clientProfile;
+ }
+
+ public void setActionHeadingLabelText(String text) {
+ actionHeadingLabel.setText(text);
+ }
+
+ private Action action;
+
+ public Action getAction() {
+ return action;
+ }
+
+ private ActionBox actionBox;
+
+ public ActionBox getActionBox() {
+ return actionBox;
+ }
+
+ @Override
+ public void onActionClicked(Action action, ActionBox actionBox) throws MinorException {
+ this.action = action;
+ this.actionBox = actionBox;
+
+ logger.info("Action Display text : "+action.getDisplayText());
+ clear();
+
+ renderActionProperties(false);
+ }
+
+ private TextField displayNameTextField;
+ private CheckBox hideDisplayTextCheckBox;
+ private CheckBox hideIconCheckBox;
+ private TextField iconFileTextField;
+ private Button clearIconButton;
+ private ColorPicker actionBackgroundColourPicker;
+ private ColorPicker displayTextColourPicker;
+ private CheckBox actionBackgroundColourTransparentCheckBox;
+ private CheckBox displayTextColourDefaultCheckBox;
+ private ComboBox<DisplayTextAlignment> displayTextAlignmentComboBox;
+
+ public void clear()
+ {
+ sendIcon = false;
+ actionClientProperties.clear();
+ displayNameTextField.clear();
+ iconFileTextField.clear();
+ clientPropertiesVBox.getChildren().clear();
+ vbox.setVisible(false);
+ scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+ buttonBar.setVisible(false);
+ setActionHeadingLabelText("");
+
+ actionBackgroundColourPicker.setValue(Color.WHITE);
+ displayTextColourPicker.setValue(Color.WHITE);
+ }
+
+ boolean isCombineChild = false;
+
+ public boolean isCombineChild() {
+ return isCombineChild;
+ }
+
+ public void renderActionProperties(boolean isCombineChild) throws MinorException
+ {
+ this.isCombineChild = isCombineChild;
+ displayNameTextField.setText(action.getDisplayText());
+
+
+
+ if(isCombineChild)
+ {
+ setReturnButtonForCombineActionChildVisible(true);
+ normalActionsPropsVBox.setVisible(false);
+ hideDisplayTextCheckBox.setSelected(false);
+ hideDisplayTextCheckBox.setVisible(false);
+ }
+ else
+ {
+ normalActionsPropsVBox.setVisible(true);
+ setReturnButtonForCombineActionChildVisible(false);
+ hideDisplayTextCheckBox.setVisible(true);
+ setFolderButtonVisible(action.getActionType().equals(ActionType.FOLDER));
+
+ displayTextAlignmentComboBox.getSelectionModel().select(action.getDisplayTextAlignment());
+
+ if(!action.getBgColourHex().isEmpty())
+ actionBackgroundColourPicker.setValue(Color.valueOf(action.getBgColourHex()));
+ else
+ actionBackgroundColourTransparentCheckBox.setSelected(true);
+
+
+
+ if(!action.getDisplayTextFontColourHex().isEmpty())
+ displayTextColourPicker.setValue(Color.valueOf(action.getDisplayTextFontColourHex()));
+ else
+ displayTextColourDefaultCheckBox.setSelected(true);
+
+
+ hideDisplayTextCheckBox.setSelected(!action.isShowDisplayText());
+
+
+ hideIconCheckBox.setDisable(!action.isHasIcon());
+
+ hideIconCheckBox.setSelected(!action.isShowIcon());
+
+ if(!action.isHasIcon())
+ {
+ hideIconCheckBox.setSelected(false);
+ }
+
+ clearIconButton.setDisable(!action.isHasIcon());
+ }
+
+
+
+ buttonBar.setVisible(true);
+ vbox.setVisible(true);
+ scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
+
+ if(action.getActionType() == ActionType.NORMAL)
+ {
+ if(action.isInvalid())
+ setActionHeadingLabelText("Invalid Action ("+action.getModuleName()+")");
+ else
+ setActionHeadingLabelText(action.getName());
+ }
+ else if(action.getActionType() == ActionType.COMBINE)
+ setActionHeadingLabelText("Combine Action");
+ else if(action.getActionType() == ActionType.FOLDER)
+ setActionHeadingLabelText("Folder Action");
+
+
+ if(!action.isInvalid())
+ {
+ if(action.getActionType() == ActionType.NORMAL)
+ renderClientProperties();
+ else if(action.getActionType() == ActionType.COMBINE)
+ renderCombineActionProperties();
+ }
+ }
+
+ private CombineActionPropertiesPane combineActionPropertiesPane;
+
+ public CombineActionPropertiesPane getCombineActionPropertiesPane() {
+ return combineActionPropertiesPane;
+ }
+
+ public void setReturnButtonForCombineActionChildVisible(boolean visible)
+ {
+ returnButtonForCombineActionChild.setVisible(visible);
+ }
+
+ public void renderCombineActionProperties()
+ {
+ try {
+ logger.info("@@@@@ : "+action.getClientProperties().getSize());
+ combineActionPropertiesPane = new CombineActionPropertiesPane(getActionAsCombineAction(action),
+ getClientProfile(),
+ this
+ );
+
+ clientPropertiesVBox.getChildren().add(combineActionPropertiesPane);
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ }
+
+ public void setAction(Action action) {
+ this.action = action;
+ }
+
+ public FolderAction getActionAsFolderAction(Action action)
+ {
+ FolderAction folderAction = new FolderAction();
+ folderAction.setDisplayText(action.getDisplayText());
+ folderAction.setName(action.getName());
+ folderAction.setID(action.getID());
+ folderAction.setLocation(action.getLocation());
+ folderAction.setBgColourHex(action.getBgColourHex());
+ folderAction.setParent(action.getParent());
+ folderAction.getClientProperties().set(action.getClientProperties());
+ folderAction.setDisplayTextAlignment(action.getDisplayTextAlignment());
+ folderAction.setShowIcon(action.isShowIcon());
+ folderAction.setHasIcon(action.isHasIcon());
+ if(folderAction.isHasIcon())
+ folderAction.setIcon(action.getIconAsByteArray());
+ folderAction.setDisplayTextFontColourHex(action.getDisplayTextFontColourHex());
+
+ return folderAction;
+ }
+
+ public CombineAction getActionAsCombineAction(Action action)
+ {
+ CombineAction combineAction = new CombineAction();
+ combineAction.setDisplayText(action.getDisplayText());
+ combineAction.setName(action.getName());
+ combineAction.setID(action.getID());
+ combineAction.setLocation(action.getLocation());
+ combineAction.setBgColourHex(action.getBgColourHex());
+ combineAction.setParent(action.getParent());
+ combineAction.getClientProperties().set(action.getClientProperties());
+ combineAction.setDisplayTextAlignment(action.getDisplayTextAlignment());
+ combineAction.setShowIcon(action.isShowIcon());
+ combineAction.setHasIcon(action.isHasIcon());
+ if(combineAction.isHasIcon())
+ combineAction.setIcon(action.getIconAsByteArray());
+ combineAction.setDisplayTextFontColourHex(action.getDisplayTextFontColourHex());
+
+ for(Property prop : combineAction.getClientProperties().get())
+ {
+ System.out.println("PROP : "+prop.getName()+","+prop.getRawValue());
+ }
+ return combineAction;
+ }
+
+ public void onOpenFolderButtonClicked()
+ {
+ FolderAction folderAction = getActionAsFolderAction(action);
+ actionBox.getActionGridPaneListener().renderFolder(folderAction);
+ clear();
+ }
+
+ @Override
+ public Window getCurrentWindow() {
+ return getScene().getWindow();
+ }
+
+ private ArrayList<UIPropertyBox> actionClientProperties;
+
+ public void renderClientProperties() throws MinorException
+ {
+ for(int i =0;i< action.getClientProperties().getSize(); i++)
+ {
+ Property eachProperty = action.getClientProperties().get().get(i);
+
+ if(!eachProperty.isVisible())
+ continue;
+
+ Label label = new Label(eachProperty.getDisplayName());
+
+ HBox hBox = new HBox(label);
+ hBox.setSpacing(5.0);
+ hBox.setAlignment(Pos.CENTER_LEFT);
+
+ Node controlNode = null;
+
+ if(eachProperty.getHelpLink() != null)
+ {
+ Button helpButton = new Button();
+ FontIcon questionIcon = new FontIcon("fas-question");
+ helpButton.setGraphic(questionIcon);
+
+ helpButton.setOnAction(event -> {
+ hostServices.showDocument(eachProperty.getHelpLink());
+ });
+
+ hBox.getChildren().add(helpButton);
+ }
+
+ hBox.getChildren().add(new SpaceFiller(SpaceFiller.FillerType.HBox));
+
+ if(eachProperty.getControlType() == ControlType.COMBO_BOX)
+ {
+ ComboBox<String> comboBox = new ComboBox<>();
+ comboBox.getItems().addAll(eachProperty.getListValue());
+ comboBox.getSelectionModel().select(eachProperty.getSelectedIndex());
+
+
+ controlNode = comboBox;
+ }
+ else if(eachProperty.getControlType() == ControlType.TEXT_FIELD)
+ {
+ TextField textField = new TextField(eachProperty.getRawValue());
+
+ controlNode= textField;
+ }
+ else if(eachProperty.getControlType() == ControlType.TOGGLE)
+ {
+ ToggleButton toggleButton = new ToggleButton();
+ toggleButton.setSelected(eachProperty.getBoolValue());
+
+ if(eachProperty.getBoolValue())
+ toggleButton.setText("ON");
+ else
+ toggleButton.setText("OFF");
+
+ toggleButton.selectedProperty().addListener((observableValue, aBoolean, t1) -> {
+ if(t1)
+ toggleButton.setText("ON");
+ else
+ toggleButton.setText("OFF");
+ });
+
+ controlNode = toggleButton;
+ }
+ else if(eachProperty.getControlType() == ControlType.SLIDER_DOUBLE)
+ {
+ Slider slider = new Slider();
+ slider.setValue(eachProperty.getDoubleValue());
+ slider.setMax(eachProperty.getMaxDoubleValue());
+ slider.setMin(eachProperty.getMinDoubleValue());
+
+ controlNode = slider;
+ }
+ else if(eachProperty.getControlType() == ControlType.SLIDER_INTEGER)
+ {
+ Slider slider = new Slider();
+ slider.setValue(eachProperty.getIntValue());
+
+ slider.setMax(eachProperty.getMaxIntValue());
+ slider.setMin(eachProperty.getMinIntValue());
+ slider.setBlockIncrement(1.0);
+ slider.setSnapToTicks(true);
+
+ controlNode = slider;
+ }
+
+
+ hBox.getChildren().add(controlNode);
+
+ UIPropertyBox clientProperty = new UIPropertyBox(i, eachProperty.getDisplayName(), controlNode,
+ eachProperty.getControlType(), eachProperty.getType(), eachProperty.isCanBeBlank());
+
+ actionClientProperties.add(clientProperty);
+
+ clientPropertiesVBox.getChildren().add(hBox);
+ }
+ }
+
+ public void onSaveButtonClicked()
+ {
+ try {
+ // saveButton.setDisable(true);
+ // deleteButton.setDisable(true);
+
+ validateForm();
+
+ saveAction();
+ }
+ catch (MinorException e)
+ {
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+
+ }
+
+ private boolean sendIcon = false;
+
+ @Override
+ public void setSendIcon(boolean sendIcon)
+ {
+ this.sendIcon = sendIcon;
+ }
+
+ public void addActionToCurrentClientProfile(Action newAction) throws CloneNotSupportedException {
+ getClientProfile().addAction(newAction);
+ }
+
+ @Override
+ public void saveAction(Action action)
+ {
+ new Thread(
+ new OnSaveActionTask(
+ ClientConnections.getInstance().getClientConnectionBySocketAddress(
+ getClient().getRemoteSocketAddress()
+ ),
+ action,
+ displayNameTextField.getText(),
+ isCombineChild(),
+ !hideDisplayTextCheckBox.isSelected(),
+ displayTextColourDefaultCheckBox.isSelected(),
+ "#" + displayTextColourPicker.getValue().toString().substring(2),
+ clearIconButton.isDisable(),
+ !hideIconCheckBox.isSelected(),
+ displayTextAlignmentComboBox.getSelectionModel().getSelectedItem(),
+ actionBackgroundColourTransparentCheckBox.isSelected(),
+ "#" + actionBackgroundColourPicker.getValue().toString().substring(2),
+ getCombineActionPropertiesPane(),
+ clientProfile, sendIcon, actionBox, actionClientProperties, exceptionAndAlertHandler,
+ saveButton, deleteButton
+ )
+ ).start();
+ }
+
+ @Override
+ public void saveAction()
+ {
+ saveAction(action);
+ }
+
+ public void setFolderButtonVisible(boolean visible)
+ {
+ openFolderButton.setVisible(visible);
+ }
+
+ public void validateForm() throws MinorException
+ {
+ String displayNameStr = displayNameTextField.getText();
+
+ StringBuilder finalErrors = new StringBuilder();
+
+ if(displayNameStr.isBlank())
+ {
+ finalErrors.append(" * Display Name cannot be blank\n");
+ }
+
+ if(!isCombineChild())
+ {
+ if(action.isHasIcon())
+ {
+ if(hideDisplayTextCheckBox.isSelected() && hideIconCheckBox.isSelected())
+ finalErrors.append(" * Both Icon and display text check box cannot be hidden.\n");
+ }
+ else
+ {
+ if(hideDisplayTextCheckBox.isSelected())
+ finalErrors.append(" * Display Text cannot be hidden, since there is also no icon.\n");
+ }
+
+ }
+
+
+
+ for (UIPropertyBox clientProperty : actionClientProperties) {
+
+ Node controlNode = clientProperty.getControlNode();
+
+ if (clientProperty.getControlType() == ControlType.TEXT_FIELD)
+ {
+ String value = ((TextField) controlNode).getText();
+ if(clientProperty.getType() == Type.INTEGER)
+ {
+ try
+ {
+ Integer.parseInt(value);
+ }
+ catch (NumberFormatException e)
+ {
+ finalErrors.append(" -> ").append(clientProperty.getDisplayName()).append(" must be integer.\n");
+ }
+ }
+ else
+ {
+ if(value.isBlank() && !clientProperty.isCanBeBlank())
+ finalErrors.append(" -> ").append(clientProperty.getDisplayName()).append(" cannot be blank.\n");
+ }
+ }
+ }
+
+
+ if(!finalErrors.toString().isEmpty())
+ {
+ throw new MinorException("You made mistakes",
+ finalErrors.toString());
+ }
+ }
+
+ public void onDeleteButtonClicked()
+ {
+ new Thread(
+ new OnDeleteActionTask(
+ ClientConnections.getInstance().getClientConnectionBySocketAddress(
+ getClient().getRemoteSocketAddress()
+ ),
+ action,
+ isCombineChild(),
+ getCombineActionPropertiesPane(),
+ clientProfile, actionBox, this, exceptionAndAlertHandler
+ )
+ ).start();
+ }
+}
\ No newline at end of file
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionsDetailPane/ActionDetailsPaneListener.java
@@ -0,0 +1,26 @@
+package com.StreamPi.Server.Window.Dashboard.ActionsDetailPane;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+import com.StreamPi.Server.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.Util.Exception.MinorException;
+import javafx.stage.Window;
+
+public interface ActionDetailsPaneListener {
+ void onActionClicked(Action action, ActionBox actionBox) throws MinorException;
+
+ void saveAction();
+
+ void saveAction(Action action);
+
+ void clear();
+
+ void setSendIcon(boolean sendIcon);
+
+ void onOpenFolderButtonClicked();
+
+ Window getCurrentWindow();
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionsDetailPane/CombineActionPropertiesPane.java
@@ -0,0 +1,183 @@
+package com.StreamPi.Server.Window.Dashboard.ActionsDetailPane;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.Location;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.ActionAPI.OtherActions.CombineAction;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Util.Exception.MinorException;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.event.ActionEvent;
+import javafx.geometry.Insets;
+import javafx.scene.Node;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.input.TransferMode;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Font;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+public class CombineActionPropertiesPane extends VBox {
+ private CombineAction combineAction;
+ private ClientProfile clientProfile;
+ private ActionDetailsPane actionDetailsPane;
+
+ public CombineAction getCombineAction() {
+ return combineAction;
+ }
+
+ public CombineActionPropertiesPane(CombineAction combineAction, ClientProfile clientProfile, ActionDetailsPane actionDetailsPane) throws MinorException {
+ this.combineAction = combineAction;
+ this.clientProfile = clientProfile;
+ this.actionDetailsPane = actionDetailsPane;
+
+ setSpacing(10.0);
+
+ setPadding(new Insets(0,0,10,0));
+
+ renderProps();
+ }
+
+
+ public void renderProps() throws MinorException {
+
+ getChildren().clear();
+
+ int i = 0;
+
+ for(String actionID : combineAction.getChildrenIDSequential())
+ {
+ Action action = clientProfile.getActionByID(actionID);
+
+ System.out.println("232323xxxxxxxxxxxx : "+action.getID());
+
+ Button settingsButton = new Button();
+
+ FontIcon settingsFontIcon = new FontIcon("fas-cog");
+ settingsButton.setGraphic(settingsFontIcon);
+
+
+ settingsButton.setOnAction(event -> onSettingsButtonClicked(action));
+
+ Button upButton = new Button();
+ FontIcon upButtonFontIcon = new FontIcon("fas-chevron-up");
+ upButton.setGraphic(upButtonFontIcon);
+
+
+ Button downButton = new Button();
+ FontIcon downButtonFontIcon = new FontIcon("fas-chevron-down");
+ downButton.setGraphic(downButtonFontIcon);
+
+ Label displayTextLabel = new Label(action.getDisplayText());
+ displayTextLabel.setId(action.getID());
+
+ Region r = new Region();
+ HBox.setHgrow(r, Priority.ALWAYS);
+
+ HBox actionHBox = new HBox(displayTextLabel, r, settingsButton, upButton, downButton);
+ actionHBox.setSpacing(5.0);
+
+ upButton.setUserData(i);
+ downButton.setUserData(i);
+
+
+ upButton.setOnAction(this::onUpButtonClicked);
+
+ downButton.setOnAction(this::onDownButtonClicked);
+
+ System.out.println("67878678678678");
+ getChildren().add(actionHBox);
+ System.out.println("9mhmmn");
+ i++;
+ }
+ }
+
+ public void onSettingsButtonClicked(Action action)
+ {
+ actionDetailsPane.clear();
+ actionDetailsPane.setAction(action);
+ try {
+ actionDetailsPane.renderActionProperties(true);
+ } catch (MinorException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void onUpButtonClicked(ActionEvent event) {
+ try {
+ Node node = (Node) event.getSource();
+
+ int currentIndex = (int) node.getUserData();
+
+ if(currentIndex > 0)
+ {
+
+ Property current = combineAction.getClientProperties().getSingleProperty(currentIndex+"");
+ Property aboveOne = combineAction.getClientProperties().getSingleProperty((currentIndex-1)+"");
+
+ combineAction.addChild(current.getRawValue(), currentIndex-1);
+ combineAction.addChild(aboveOne.getRawValue(), currentIndex);
+
+ actionDetailsPane.saveAction();
+ renderProps();
+ }
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+
+ public void onDownButtonClicked(ActionEvent event)
+ {
+ try {
+ Node node = (Node) event.getSource();
+
+ int currentIndex = (int) node.getUserData();
+
+ if(currentIndex < getChildren().size() - 1)
+ {
+
+ Property current = combineAction.getClientProperties().getSingleProperty(currentIndex+"");
+ Property belowOne = combineAction.getClientProperties().getSingleProperty((currentIndex+1)+"");
+
+ combineAction.addChild(current.getRawValue(), currentIndex+1);
+ combineAction.addChild(belowOne.getRawValue(), currentIndex);
+
+ actionDetailsPane.saveAction();
+ renderProps();
+ }
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+
+ public List<String> getFinalChildren()
+ {
+ ArrayList<String> children = new ArrayList<>();
+
+ for(int i = 0;i<getChildren().size();i++)
+ {
+ HBox hBox = (HBox) getChildren().get(i);
+ Label label = (Label) hBox.getChildren().get(0);
+ children.add(label.getId());
+ }
+
+ return children;
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionsDetailPane/OnDeleteActionTask.java
@@ -0,0 +1,112 @@
+package com.StreamPi.Server.Window.Dashboard.ActionsDetailPane;
+
+import java.util.logging.Logger;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Server.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+
+public class OnDeleteActionTask extends Task<Void>
+{
+ private Logger logger;
+
+ public OnDeleteActionTask(ClientConnection connection, Action action, boolean isCombineChild,
+ CombineActionPropertiesPane combineActionPropertiesPane, ClientProfile clientProfile, ActionBox actionBox,
+ ActionDetailsPane actionDetailsPane,
+ ExceptionAndAlertHandler exceptionAndAlertHandler)
+ {
+ this.connection = connection;
+ this.action = action;
+ this.isCombineChild = isCombineChild;
+ this.combineActionPropertiesPane = combineActionPropertiesPane;
+ this.clientProfile = clientProfile;
+ this.actionBox = actionBox;
+ this.actionDetailsPane = actionDetailsPane;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+
+ logger = Logger.getLogger(getClass().getName());
+ }
+
+ private ClientConnection connection;
+ private Action action;
+ private ActionBox actionBox;
+ private ActionDetailsPane actionDetailsPane;
+ private boolean isCombineChild;
+ private ClientProfile clientProfile;
+ private CombineActionPropertiesPane combineActionPropertiesPane;
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+
+ private void runTask()
+ {
+ try {
+
+ connection.deleteAction(clientProfile.getID(), action.getID());
+ clientProfile.removeActionByID(action.getID());
+
+ if(isCombineChild)
+ {
+ System.out.println("ACTION ID TO BE REMOVED : "+action.getID());
+ combineActionPropertiesPane.getCombineAction().removeChild(action.getID());
+
+ System.out.println("222155 "+combineActionPropertiesPane.getCombineAction().getClientProperties().getSize());
+
+ combineActionPropertiesPane.renderProps();
+
+ try {
+
+ actionDetailsPane.onActionClicked(
+ combineActionPropertiesPane.getCombineAction(),
+ actionBox
+ );
+
+ actionDetailsPane.saveAction(combineActionPropertiesPane.getCombineAction());
+
+ } catch (MinorException e) {
+ e.printStackTrace();
+ }
+ }
+ else
+ {
+ Platform.runLater(()->{
+ actionBox.clear();
+ actionDetailsPane.clear();
+ });
+ }
+
+
+ // Platform.runLater(()->{
+ // saveButton.setDisable(false);
+ // deleteButton.setDisable(false);
+ // });
+
+ }
+ catch (SevereException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ }
+
+
+ @Override
+ protected Void call() throws Exception
+ {
+ runTask();
+ return null;
+ }
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ActionsDetailPane/OnSaveActionTask.java
@@ -0,0 +1,224 @@
+package com.StreamPi.Server.Window.Dashboard.ActionsDetailPane;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.ActionProperty.ClientProperties;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.Server.UIPropertyBox.UIPropertyBox;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Server.Window.Dashboard.ActionGridPane.ActionBox;
+import com.StreamPi.Util.Exception.SevereException;
+
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.scene.control.Button;
+
+public class OnSaveActionTask extends Task<Void>
+{
+
+ private Logger logger;
+
+ public OnSaveActionTask(ClientConnection connection, Action action, String displayNameText, boolean isCombineChild,
+ boolean isShowDisplayText, boolean isDefaultDisplayTextColour, String displayTextFontColour, boolean isClearIcon,
+ boolean isHideIcon, DisplayTextAlignment displayTextAlignment, boolean isTransparentBackground, String backgroundColour,
+ CombineActionPropertiesPane combineActionPropertiesPane, ClientProfile clientProfile, boolean sendIcon, ActionBox actionBox,
+ ArrayList<UIPropertyBox> actionClientProperties, ExceptionAndAlertHandler exceptionAndAlertHandler, Button saveButton, Button deleteButton)
+ {
+ this.saveButton = saveButton;
+ this.deleteButton = deleteButton;
+
+ this.connection = connection;
+ this.action = action;
+ this.displayNameText = displayNameText;
+ this.isCombineChild = isCombineChild;
+ this.isShowDisplayText = isShowDisplayText;
+ this.isDefaultDisplayTextColour = isDefaultDisplayTextColour;
+ this.displayTextFontColour = displayTextFontColour;
+ this.isClearIcon = isClearIcon;
+ this.isHideIcon = isHideIcon;
+ this.displayTextAlignment = displayTextAlignment;
+ this.isTransparentBackground = isTransparentBackground;
+ this.combineActionPropertiesPane = combineActionPropertiesPane;
+ this.clientProfile = clientProfile;
+ this.sendIcon = sendIcon;
+ this.actionBox = actionBox;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.backgroundColour = backgroundColour;
+ this.actionClientProperties = actionClientProperties;
+
+
+ logger = Logger.getLogger(getClass().getName());
+ }
+
+ private Button saveButton;
+ private Button deleteButton;
+ private boolean isShowDisplayText;
+ private boolean isCombineChild;
+ private String displayNameText;
+ private boolean isDefaultDisplayTextColour;
+ private ArrayList<UIPropertyBox> actionClientProperties;
+ private String displayTextFontColour;
+ private boolean isClearIcon;
+ private boolean isHideIcon;
+ private DisplayTextAlignment displayTextAlignment;
+ private boolean isTransparentBackground;
+ private String backgroundColour;
+ private CombineActionPropertiesPane combineActionPropertiesPane;
+ private ClientProfile clientProfile;
+ private boolean sendIcon;
+ private ActionBox actionBox;
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+ private Action action;
+ private ClientConnection connection;
+
+ private void setSaveDeleteButtonState(boolean state)
+ {
+ Platform.runLater(()->{
+ saveButton.setDisable(state);
+ deleteButton.setDisable(state);
+ });
+ }
+ private void runTask()
+ {
+ action.setDisplayText(displayNameText);
+
+ if(!isCombineChild)
+ {
+ setSaveDeleteButtonState(true);
+
+ action.setShowDisplayText(isShowDisplayText);
+
+ if(isDefaultDisplayTextColour)
+ action.setDisplayTextFontColourHex("");
+ else
+ {
+ action.setDisplayTextFontColourHex(displayTextFontColour);
+ //String fontColour = "#" + displayTextColourPicker.getValue().toString().substring(2);
+ //action.setDisplayTextFontColourHex(fontColour);
+ }
+
+
+ if(isClearIcon)
+ {
+ action.setIcon(null);
+ action.setHasIcon(false);
+ action.setShowIcon(false);
+ }
+
+ if(action.isHasIcon())
+ action.setShowIcon(isHideIcon);
+
+
+ action.setDisplayTextAlignment(displayTextAlignment);
+
+
+ logger.info("BBBGGG : "+backgroundColour);
+ if(isTransparentBackground)
+ action.setBgColourHex("");
+ else
+ {
+ //String bgColour = "#" + actionBackgroundColourPicker.getValue().toString().substring(2);
+ action.setBgColourHex(backgroundColour);
+ }
+ }
+
+ System.out.println("parent : "+action.getParent());
+
+
+ if(action.getActionType() == ActionType.COMBINE)
+ {
+ List<String> finalChildren = combineActionPropertiesPane.getFinalChildren();
+ System.out.println("2334 "+finalChildren.size());
+
+ ClientProperties clientProperties = new ClientProperties();
+
+ for(int i = 0;i<finalChildren.size();i++)
+ {
+ Property property = new Property(i+"", Type.STRING);
+ property.setRawValue(finalChildren.get(i));
+
+ clientProperties.addProperty(property);
+ }
+
+ action.getClientProperties().set(clientProperties);
+ }
+ else
+ {
+ //properties
+ for (UIPropertyBox clientProperty : actionClientProperties) {
+ action.getClientProperties().get().get(clientProperty.getIndex()).setRawValue(clientProperty.getRawValue());
+ }
+ }
+
+
+ try
+ {
+ logger.info("Saving action ... "+action.isHasIcon()+"+"+sendIcon);
+
+ if(action.isHasIcon())
+ {
+ if(clientProfile.getActionByID(action.getID()).getIconAsByteArray() == null)
+ {
+ sendIcon = true;
+ }
+ else
+ {
+ if(!Arrays.equals(action.getIconAsByteArray(), clientProfile.getActionByID(action.getID()).getIconAsByteArray()))
+ {
+ logger.info("Sending ...");
+ sendIcon = true;
+ }
+ }
+ }
+
+ connection.saveActionDetails(clientProfile.getID(), action);
+
+ if(sendIcon)
+ {
+ connection.sendIcon(clientProfile.getID(), action.getID(), action.getIconAsByteArray());
+ }
+
+ if(!isCombineChild)
+ {
+ Platform.runLater(()->{
+ actionBox.clear();
+ actionBox.setAction(action);
+ actionBox.baseInit();
+ actionBox.init();
+ });
+
+ setSaveDeleteButtonState(false);
+ }
+
+ clientProfile.removeActionByID(action.getID());
+ clientProfile.addAction(action);
+
+ }
+ catch (SevereException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ catch (CloneNotSupportedException e)
+ {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ protected Void call() throws Exception
+ {
+ runTask();
+ return null;
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/ClientDetailsPane.java
@@ -0,0 +1,167 @@
+package com.StreamPi.Server.Window.Dashboard;
+
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.Server.Connection.ClientConnections;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.geometry.Insets;
+import javafx.scene.CacheHint;
+import javafx.scene.control.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Paint;
+import javafx.util.Callback;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+public class ClientDetailsPane extends HBox {
+
+ private DashboardInterface dashboard;
+
+ public ClientDetailsPane(DashboardInterface dashboard)
+ {
+ this.dashboard = dashboard;
+
+ VBox.setVgrow(this, Priority.NEVER);
+ getStyleClass().add("client_details_pane");
+ setPadding(new Insets(10));
+ setMinHeight(90);
+
+ initUI();
+ loadData();
+
+ setCache(true);
+ setCacheHint(CacheHint.SPEED);
+ }
+
+ private ComboBox<ClientConnection> clientsComboBox;
+ private Label noClientsConnectedLabel;
+ private ComboBox<ClientProfile> clientProfilesComboBox;
+
+
+ public void initUI()
+ {
+ noClientsConnectedLabel = new Label("No Clients Connected");
+ noClientsConnectedLabel.managedProperty().bind(noClientsConnectedLabel.visibleProperty());
+
+ clientsComboBox = new ComboBox<>();
+ clientsComboBox.setPromptText("Choose Client");
+
+ clientsComboBox.valueProperty().addListener((observableValue, oldVal, newVal) -> {
+ if(oldVal!=newVal && newVal!=null)
+ {
+ dashboard.newSelectedClientConnection(newVal);
+ clientProfilesComboBox.setItems(FXCollections.observableArrayList(newVal.getClient().getAllClientProfiles()));
+ clientProfilesComboBox.setVisible(true);
+ }
+ });
+
+ clientsComboBox.managedProperty().bind(clientsComboBox.visibleProperty());
+
+
+ Callback<ListView<ClientConnection>, ListCell<ClientConnection>> clientsComboBoxFactory = new Callback<>() {
+ @Override
+ public ListCell<ClientConnection> call(ListView<ClientConnection> clientConnectionListView) {
+
+ return new ListCell<>() {
+ @Override
+ protected void updateItem(ClientConnection clientConnection, boolean b) {
+ super.updateItem(clientConnection, b);
+
+ if(clientConnection == null)
+ {
+ setText("Choose Client");
+ }
+ else
+ {
+ Client client = clientConnection.getClient();
+ setText(client.getNickName());
+ }
+ }
+ };
+ }
+ };
+ clientsComboBox.setCellFactory(clientsComboBoxFactory);
+ clientsComboBox.setButtonCell(clientsComboBoxFactory.call(null));
+
+
+
+ clientProfilesComboBox = new ComboBox<>();
+ clientProfilesComboBox.setPromptText("Choose Profile");
+ clientProfilesComboBox.valueProperty().addListener((observableValue, oldVal, newVal) -> {
+ if(oldVal!=newVal && newVal!=null)
+ {
+ dashboard.newSelectedClientProfile(newVal);
+ }
+ });
+
+
+ clientProfilesComboBox.managedProperty().bind(clientProfilesComboBox.visibleProperty());
+ Callback<ListView<ClientProfile>, ListCell<ClientProfile>> clientProfilesComboBoxFactory = new Callback<ListView<ClientProfile>, ListCell<ClientProfile>>() {
+ @Override
+ public ListCell<ClientProfile> call(ListView<ClientProfile> clientProfileListView) {
+ return new ListCell<>()
+ {
+ @Override
+ protected void updateItem(ClientProfile profile, boolean b) {
+ super.updateItem(profile, b);
+
+ if(profile == null)
+ {
+ setText("Choose Profile");
+ }
+ else
+ {
+ setText(profile.getName());
+ }
+ }
+ };
+ }
+ };
+ clientProfilesComboBox.setCellFactory(clientProfilesComboBoxFactory);
+ clientProfilesComboBox.setButtonCell(clientProfilesComboBoxFactory.call(null));
+
+ VBox stack = new VBox(noClientsConnectedLabel, clientsComboBox, clientProfilesComboBox);
+ stack.setSpacing(10);
+
+ getChildren().addAll(stack);
+
+ }
+
+
+ private void loadData()
+ {
+ clientsComboBox.getSelectionModel().clearSelection();
+ clientProfilesComboBox.getSelectionModel().clearSelection();
+
+ if(ClientConnections.getInstance().getConnections().size() == 0)
+ {
+ noClientsConnectedLabel.setVisible(true);
+
+ clientsComboBox.setVisible(false);
+
+ clientProfilesComboBox.setVisible(false);
+
+
+ dashboard.newSelectedClientConnection(null);
+
+ }
+ else
+ {
+ noClientsConnectedLabel.setVisible(false);
+
+ clientsComboBox.setVisible(true);
+ clientProfilesComboBox.setVisible(false);
+
+ clientsComboBox.setItems(FXCollections.observableArrayList(ClientConnections.getInstance().getConnections()));
+ }
+ }
+
+ public void refresh()
+ {
+ loadData();
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/DashboardBase.java
@@ -0,0 +1,137 @@
+package com.StreamPi.Server.Window.Dashboard;
+
+import java.util.logging.Logger;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.Server.Window.Dashboard.ActionGridPane.ActionGridPane;
+import com.StreamPi.Server.Window.Dashboard.ActionsDetailPane.ActionDetailsPane;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Exception.SevereException;
+import javafx.application.HostServices;
+import javafx.scene.CacheHint;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+
+public class DashboardBase extends HBox implements DashboardInterface {
+
+ private final VBox leftPane;
+
+ private Logger logger;
+
+ public ClientProfile currentClientProfile;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+
+ public DashboardBase(ExceptionAndAlertHandler exceptionAndAlertHandler, HostServices hostServices)
+ {
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ logger = Logger.getLogger(DashboardBase.class.getName());
+ leftPane = new VBox();
+
+ HBox.setHgrow(leftPane, Priority.ALWAYS);
+ getChildren().add(leftPane);
+
+ setPluginsPane(new PluginsPane(hostServices));
+
+ setClientDetailsPane(new ClientDetailsPane(this));
+
+ setActionGridPane(new ActionGridPane(exceptionAndAlertHandler));
+
+ setActionDetailsPane(new ActionDetailsPane(exceptionAndAlertHandler, hostServices));
+
+ getActionGridPane().setActionDetailsPaneListener(getActionDetailsPane());
+ }
+
+
+
+ private PluginsPane pluginsPane;
+ private void setPluginsPane(PluginsPane pluginsPane)
+ {
+ this.pluginsPane = pluginsPane;
+ getChildren().add(this.pluginsPane);
+ }
+ public PluginsPane getPluginsPane()
+ {
+ return pluginsPane;
+ }
+
+ private ClientDetailsPane clientDetailsPane;
+ private void setClientDetailsPane(ClientDetailsPane clientDetailsPane)
+ {
+ this.clientDetailsPane = clientDetailsPane;
+ leftPane.getChildren().add(this.clientDetailsPane);
+ }
+ public ClientDetailsPane getClientDetailsPane()
+ {
+ return clientDetailsPane;
+ }
+
+ private ActionGridPane actionGridPane;
+ private void setActionGridPane(ActionGridPane actionGridPane)
+ {
+ this.actionGridPane = actionGridPane;
+ leftPane.getChildren().add(this.actionGridPane);
+ }
+ public ActionGridPane getActionGridPane()
+ {
+ return actionGridPane;
+ }
+
+ private ActionDetailsPane actionDetailsPane;
+ private void setActionDetailsPane(ActionDetailsPane actionDetailsPane)
+ {
+ this.actionDetailsPane = actionDetailsPane;
+ leftPane.getChildren().add(this.actionDetailsPane);
+ }
+ public ActionDetailsPane getActionDetailsPane()
+ {
+ return actionDetailsPane;
+ }
+
+ public void newSelectedClientConnection(ClientConnection clientConnection)
+ {
+ if(clientConnection == null)
+ {
+ logger.info("Remove action grid");
+ }
+ else
+ {
+ getActionDetailsPane().setClient(clientConnection.getClient());
+ getActionGridPane().setClient(clientConnection.getClient());
+
+ logger.info("Current selected client details:");
+ clientConnection.getClient().debugPrint();
+ }
+ }
+
+ public void newSelectedClientProfile(ClientProfile clientProfile)
+ {
+ this.currentClientProfile = clientProfile;
+
+ getActionDetailsPane().setClientProfile(clientProfile);
+
+ drawProfile(this.currentClientProfile);
+ }
+
+ public void drawProfile(ClientProfile clientProfile)
+ {
+ logger.info("Drawing ...");
+
+ getActionGridPane().setClientProfile(clientProfile);
+
+ try {
+ getActionGridPane().renderGrid();
+ getActionGridPane().renderActions();
+ }
+ catch (SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+
+
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/DashboardInterface.java
@@ -0,0 +1,10 @@
+package com.StreamPi.Server.Window.Dashboard;
+
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Connection.ClientConnection;
+
+public interface DashboardInterface {
+ void newSelectedClientConnection(ClientConnection clientConnection);
+ void newSelectedClientProfile(ClientProfile clientProfile);
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/DonatePopupContent.java
@@ -0,0 +1,65 @@
+package com.StreamPi.Server.Window.Dashboard;
+
+import com.StreamPi.Server.IO.Config;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Alert.StreamPiAlertListener;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+
+import javafx.application.HostServices;
+import javafx.scene.control.CheckBox;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.layout.VBox;
+
+public class DonatePopupContent{
+ public DonatePopupContent(HostServices hostServices, ExceptionAndAlertHandler exceptionAndAlertHandler)
+ {
+ Label label = new Label("We are a very small team working very hard on this project for best user experience.\n\n" +
+ "Something like StreamPi takes time, effort and resources. But we will always keep this 100% opensource and free.\n\n" +
+ "If you find this project helpful, and would want to help us, please consider donating :)\n\n"+
+ "If you are unable to do so even a small shoutout and share across social media would be very helpful.");
+
+ label.setWrapText(true);
+ label.getStyleClass().add("donate_request_popup_label");
+
+ Hyperlink patreonLink = new Hyperlink("Our Patreon");
+ patreonLink.setOnAction(event ->{
+ hostServices.showDocument("https://patreon.com/streampi");
+ });
+ patreonLink.getStyleClass().add("donate_request_popup_patreon_link");
+
+ VBox pane = new VBox(label, patreonLink);
+ pane.setSpacing(5.0);
+
+ streamPiAlert = new StreamPiAlert("Hey!", StreamPiAlertType.INFORMATION, pane);
+
+ streamPiAlert.setOnClicked(new StreamPiAlertListener()
+ {
+ @Override
+ public void onClick(String buttonClicked)
+ {
+ try
+ {
+ Config.getInstance().setAllowDonatePopup(false);
+ Config.getInstance().save();
+ }
+ catch(SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ }
+
+ });
+ }
+
+ private StreamPiAlert streamPiAlert;
+
+ public void show()
+ {
+ streamPiAlert.show();
+ }
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Dashboard/PluginsPane.java
@@ -0,0 +1,268 @@
+package com.StreamPi.Server.Window.Dashboard;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.Action.ActionType;
+import com.StreamPi.ActionAPI.Action.DisplayTextAlignment;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.ActionAPI.OtherActions.CombineAction;
+import com.StreamPi.ActionAPI.OtherActions.FolderAction;
+import com.StreamPi.Server.Action.NormalActionPlugins;
+
+import javafx.application.HostServices;
+import javafx.event.EventHandler;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+import javafx.scene.image.ImageView;
+import javafx.scene.input.ClipboardContent;
+import javafx.scene.input.Dragboard;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.input.TransferMode;
+import javafx.scene.layout.*;
+import javafx.scene.paint.Paint;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class PluginsPane extends VBox {
+
+ private Button settingsButton;
+
+ public PluginsPane(HostServices hostServices)
+ {
+ setMinWidth(250);
+ getStyleClass().add("plugins_pane");
+ setPadding(new Insets(10));
+
+ setSpacing(10.0);
+
+ this.hostServices = hostServices;
+
+ initUI();
+ }
+
+ private Accordion pluginsAccordion;
+
+ public void initUI()
+ {
+ pluginsAccordion = new Accordion();
+ pluginsAccordion.setCache(true);
+
+ Region r = new Region();
+ VBox.setVgrow(r, Priority.ALWAYS);
+
+ settingsButton = new Button();
+
+ FontIcon cog = new FontIcon("fas-cog");
+
+ settingsButton.setGraphic(cog);
+
+ HBox settingsHBox = new HBox(settingsButton);
+ settingsHBox.setAlignment(Pos.CENTER_RIGHT);
+
+ getChildren().addAll(new Label("Plugins"), pluginsAccordion, r, settingsHBox);
+ }
+
+ public Button getSettingsButton()
+ {
+ return settingsButton;
+ }
+
+ public void clearData()
+ {
+ pluginsAccordion.getPanes().clear();
+ }
+
+ public void loadData()
+ {
+ HashMap<String, ArrayList<NormalAction>> sortedPlugins = NormalActionPlugins.getInstance().getSortedPlugins();
+
+ for(String eachCategory : sortedPlugins.keySet())
+ {
+ VBox vBox = new VBox();
+ vBox.setSpacing(5);
+
+
+ TitledPane pane = new TitledPane(eachCategory, vBox);
+ for(NormalAction eachAction : sortedPlugins.get(eachCategory))
+ {
+ if(!eachAction.isVisibleInPluginsPane())
+ continue;
+
+ Button eachNormalActionPluginButton = new Button();
+ HBox.setHgrow(eachNormalActionPluginButton, Priority.ALWAYS);
+ eachNormalActionPluginButton.setMaxWidth(Double.MAX_VALUE);
+ eachNormalActionPluginButton.setAlignment(Pos.CENTER_LEFT);
+
+ Node graphic = eachAction.getServerButtonGraphic();
+
+ if(graphic == null)
+ {
+ FontIcon cogs = new FontIcon("fas-cogs");
+ cogs.getStyleClass().add("dashboard_plugins_pane_action_icon");
+ eachNormalActionPluginButton.setGraphic(cogs);
+ }
+ else
+ {
+ if(graphic instanceof FontIcon)
+ {
+ FontIcon fi = (FontIcon) graphic;
+ eachNormalActionPluginButton.setGraphic(fi);
+ }
+ else if(graphic instanceof ImageView)
+ {
+ ImageView iv = (ImageView) graphic;
+ iv.getStyleClass().add("dashboard_plugins_pane_action_icon_imageview");
+ iv.setPreserveRatio(false);
+ eachNormalActionPluginButton.setGraphic(iv);
+ }
+ }
+ eachNormalActionPluginButton.setText(eachAction.getName());
+
+
+ eachNormalActionPluginButton.setOnDragDetected(mouseEvent -> {
+ Dragboard db = eachNormalActionPluginButton.startDragAndDrop(TransferMode.ANY);
+
+ ClipboardContent content = new ClipboardContent();
+
+ content.put(Action.getDataFormat(), createFakeAction(eachAction, "Untitled Action"));
+
+ db.setContent(content);
+
+ mouseEvent.consume();
+ });
+
+
+
+ HBox hBox = new HBox(eachNormalActionPluginButton);
+ hBox.setSpacing(5.0);
+ hBox.setAlignment(Pos.TOP_LEFT);
+
+ HBox.setHgrow(eachNormalActionPluginButton, Priority.ALWAYS);
+
+ if(eachAction.getRepo() != null) {
+ Button helpButton = new Button();
+ FontIcon questionIcon = new FontIcon("fas-question");
+ questionIcon.getStyleClass().add("dashboard_plugins_pane_action_help_icon");
+ helpButton.setGraphic(questionIcon);
+
+ helpButton.setOnAction(event -> {
+ hostServices.showDocument(eachAction.getRepo());
+ });
+
+ hBox.getChildren().add(helpButton);
+ }
+
+
+ vBox.getChildren().add(hBox);
+ }
+
+ if(vBox.getChildren().size() > 0)
+ pluginsAccordion.getPanes().add(pane);
+ }
+ }
+
+
+ private HostServices hostServices;
+
+ public Action createFakeAction(Action action, String displayText)
+ {
+ Action newAction = new Action(action.getActionType());
+
+ if(action.getActionType() == ActionType.NORMAL)
+ {
+ newAction.setModuleName(action.getModuleName());
+ newAction.setVersion(action.getVersion());
+ newAction.setName(action.getName());
+ }
+
+ newAction.setClientProperties(action.getClientProperties());
+
+ for(Property property : newAction.getClientProperties().get())
+ {
+ if(property.getType() == Type.STRING || property.getType() == Type.INTEGER || property.getType() == Type.DOUBLE)
+ property.setRawValue(property.getDefaultRawValue());
+ }
+
+ // newAction.setLocation(location);
+
+ newAction.setIDRandom();
+
+
+ newAction.setShowDisplayText(true);
+ newAction.setDisplayText(displayText);
+ newAction.setDisplayTextAlignment(DisplayTextAlignment.CENTER);
+ newAction.setShowIcon(false);
+ newAction.setHasIcon(false);
+
+ //action.setParent(root);
+
+ newAction.setBgColourHex("");
+ newAction.setDisplayTextFontColourHex("");
+
+ return newAction;
+ }
+
+ public void loadOtherActions()
+ {
+ VBox vBox = new VBox();
+
+ Button folderActionButton = new Button("Folder");
+ folderActionButton.setMaxWidth(Double.MAX_VALUE);
+ folderActionButton.setAlignment(Pos.CENTER_LEFT);
+ FontIcon folder = new FontIcon("fas-folder");
+ folderActionButton.setGraphic(folder);
+
+ folderActionButton.setOnDragDetected(mouseEvent -> {
+ Dragboard db = folderActionButton.startDragAndDrop(TransferMode.ANY);
+
+ ClipboardContent content = new ClipboardContent();
+
+ content.put(Action.getDataFormat(), createFakeAction(new FolderAction(), "Untitled Folder"));
+
+ db.setContent(content);
+
+ mouseEvent.consume();
+ });
+
+
+
+
+ Button combineActionButton = new Button("Combine");
+ combineActionButton.setMaxWidth(Double.MAX_VALUE);
+ combineActionButton.setAlignment(Pos.CENTER_LEFT);
+ FontIcon list = new FontIcon("fas-list");
+ combineActionButton.setGraphic(list);
+
+ combineActionButton.setOnDragDetected(mouseEvent -> {
+ Dragboard db = combineActionButton.startDragAndDrop(TransferMode.ANY);
+
+ ClipboardContent content = new ClipboardContent();
+
+ content.put(Action.getDataFormat(), createFakeAction(new CombineAction(), "Untitled Combine"));
+
+ db.setContent(content);
+
+ mouseEvent.consume();
+ });
+
+
+
+
+
+ vBox.getChildren().addAll(folderActionButton, combineActionButton);
+ vBox.setSpacing(5);
+
+ TitledPane pane = new TitledPane("StreamPi", vBox);
+
+ pluginsAccordion.getPanes().add(pane);
+ pluginsAccordion.setCache(true);
+ pluginsAccordion.setCacheHint(CacheHint.SPEED);
+ }
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/ExceptionAndAlertHandler.java
@@ -0,0 +1,11 @@
+package com.StreamPi.Server.Window;
+
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import javafx.scene.control.Alert;
+
+public interface ExceptionAndAlertHandler {
+ void handleMinorException(MinorException e);
+ void handleSevereException(SevereException e);
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/FirstTimeUse/FinalConfigPane.java
@@ -0,0 +1,108 @@
+package com.StreamPi.Server.Window.FirstTimeUse;
+
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.IO.Config;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.FormHelper.HBoxInputBox;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import com.StreamPi.Util.FormHelper.SpaceFiller.FillerType;
+
+import javafx.geometry.Pos;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextField;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+
+public class FinalConfigPane extends VBox
+{
+ private TextField serverNicknameTextField;
+ private TextField serverPortTextField;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+ private ServerListener serverListener;
+
+ public FinalConfigPane(ExceptionAndAlertHandler exceptionAndAlertHandler, ServerListener serverListener)
+ {
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.serverListener = serverListener;
+
+ getStyleClass().add("first_time_use_pane_final_config");
+
+ Label label = new Label("Thats it. Now just name your Stream-Pi Server, and the port where the server will run on!\n" +
+ "You can leave the default value of the port as it is.");
+ label.setWrapText(true);
+ label.getStyleClass().add("first_time_use_pane_final_config_label");
+
+ serverNicknameTextField = new TextField();
+ serverPortTextField = new TextField("2004");
+
+ HBoxInputBox serverNickNameInputBox = new HBoxInputBox("Server Nickname", serverNicknameTextField, 200);
+ HBoxInputBox serverPortInputBox = new HBoxInputBox("Server Nickname", serverPortTextField);
+
+ Button confirmButton = new Button("Confirm");
+ confirmButton.setOnAction(event -> onConfirmButtonClicked());
+ HBox bBar = new HBox(confirmButton);
+ bBar.setAlignment(Pos.CENTER_RIGHT);
+
+ getChildren().addAll(label, serverNickNameInputBox, serverPortInputBox, new SpaceFiller(FillerType.VBox), bBar);
+
+ setSpacing(10.0);
+
+ setVisible(false);
+ }
+
+ private void onConfirmButtonClicked()
+ {
+ StringBuilder errors = new StringBuilder();
+
+ String serverNameStr = serverNicknameTextField.getText();
+ String serverPortStr = serverPortTextField.getText();
+
+ if(serverNameStr.isBlank() || serverNameStr.isEmpty())
+ {
+ errors.append("* Server Name cannot be blank.\n");
+ }
+
+ int serverPort=-1;
+ try {
+ serverPort = Integer.parseInt(serverPortStr);
+
+ if (serverPort < 1024)
+ errors.append("* Server Port must be more than 1024");
+ }
+ catch (NumberFormatException e)
+ {
+ errors.append("* Server Port must be integer.\n");
+ }
+
+ if(errors.toString().isEmpty())
+ {
+ try
+ {
+ Config.getInstance().setServerName(serverNameStr);
+ Config.getInstance().setServerPort(serverPort);
+ Config.getInstance().setFirstTimeUse(false);
+ Config.getInstance().save();
+
+ serverListener.othInit();
+
+ ((Stage) getScene().getWindow()).close();
+ }
+ catch(SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ }
+ else
+ {
+ Alert alert = new Alert(AlertType.ERROR);
+ alert.setContentText("Please rectify the following errors and try again:\n"+errors.toString());
+ alert.show();
+ }
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/FirstTimeUse/FirstTimeUse.java
@@ -0,0 +1,146 @@
+package com.StreamPi.Server.Window.FirstTimeUse;
+
+import com.StreamPi.Server.Main;
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import com.StreamPi.Util.FormHelper.SpaceFiller.FillerType;
+
+import javafx.event.ActionEvent;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.text.Font;
+
+public class FirstTimeUse extends VBox{
+
+
+ public FirstTimeUse(ExceptionAndAlertHandler exceptionAndAlertHandler, ServerListener serverListener)
+ {
+ Font.loadFont(Main.class.getResourceAsStream("Roboto.ttf"), 13);
+ getStylesheets().add(Main.class.getResource("style.css").toExternalForm());
+
+ getStyleClass().add("first_time_use_pane");
+
+ setSpacing(10.0);
+ setPadding(new Insets(5));
+
+ headingLabel = new Label();
+ headingLabel.getStyleClass().add("first_time_use_pane_heading_label");
+
+ StackPane stackPane = new StackPane();
+ stackPane.getStyleClass().add("first_time_use_pane_stackpane");
+
+ VBox.setVgrow(stackPane, Priority.ALWAYS);
+
+ welcomePane = new WelcomePane();
+ licensePane = new LicensePane();
+ finalConfigPane = new FinalConfigPane(exceptionAndAlertHandler, serverListener);
+
+ stackPane.getChildren().addAll(
+ welcomePane,
+ licensePane,
+ finalConfigPane
+ );
+
+
+ nextButton = new Button("Next");
+ nextButton.setOnAction(event-> onNextButtonClicked());
+
+ previousButton = new Button("Previous");
+ previousButton.setOnAction(event-> onPreviousButtonClicked());
+
+
+ HBox buttonBar = new HBox(previousButton, new SpaceFiller(FillerType.HBox), nextButton);
+ buttonBar.setSpacing(10.0);
+
+ getChildren().addAll(headingLabel, stackPane, buttonBar);
+
+ setWindow(WindowName.WELCOME);
+ }
+
+ private Label headingLabel;
+ private Button nextButton;
+ private Button previousButton;
+ private WelcomePane welcomePane;
+ private LicensePane licensePane;
+ private FinalConfigPane finalConfigPane;
+
+ private WindowName windowName;
+
+ private void onNextButtonClicked()
+ {
+ if(windowName == WindowName.WELCOME)
+ {
+ setWindow(WindowName.LICENSE);
+ }
+ else if(windowName == WindowName.LICENSE)
+ {
+ setWindow(WindowName.FINAL);
+ }
+ }
+
+ private void onPreviousButtonClicked()
+ {
+ if(windowName == WindowName.FINAL)
+ {
+ setWindow(WindowName.LICENSE);
+ }
+ else if(windowName == WindowName.LICENSE)
+ {
+ setWindow(WindowName.WELCOME);
+ }
+ }
+
+ private void setWindow(WindowName windowName)
+ {
+ if (windowName == WindowName.WELCOME)
+ {
+ this.windowName = WindowName.WELCOME;
+ welcomePane.toFront();
+ welcomePane.setVisible(true);
+ licensePane.setVisible(false);
+ finalConfigPane.setVisible(false);
+
+ headingLabel.setText("Welcome!");
+
+ nextButton.setDisable(false);
+ previousButton.setDisable(true);
+ }
+ else if (windowName == WindowName.LICENSE)
+ {
+ this.windowName = WindowName.LICENSE;
+ licensePane.toFront();
+ welcomePane.setVisible(false);
+ licensePane.setVisible(true);
+ finalConfigPane.setVisible(false);
+
+ headingLabel.setText("License Agreement");
+
+ nextButton.setDisable(false);
+ previousButton.setDisable(false);
+ }
+ else if (windowName == WindowName.FINAL)
+ {
+ this.windowName = WindowName.FINAL;
+ finalConfigPane.toFront();
+ welcomePane.setVisible(false);
+ licensePane.setVisible(false);
+ finalConfigPane.setVisible(true);
+
+ headingLabel.setText("Finishing up ...");
+
+ nextButton.setDisable(true);
+ previousButton.setDisable(false);
+ }
+ }
+
+
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/FirstTimeUse/LicensePane.java
@@ -0,0 +1,28 @@
+package com.StreamPi.Server.Window.FirstTimeUse;
+
+import com.StreamPi.Server.Info.License;
+
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+
+public class LicensePane extends VBox {
+ public LicensePane()
+ {
+ getStyleClass().add("first_time_use_pane_license");
+
+ Label label = new Label("By Clicking on 'Next', you agree with the license of StreamPi, and the license(s) of the library(s) used/included with this software.");
+ label.prefWidthProperty().bind(widthProperty());
+ label.setWrapText(true);
+
+ TextArea licenseTextArea = new TextArea(License.getLicense());
+ licenseTextArea.setWrapText(false);
+ licenseTextArea.setEditable(false);
+
+ licenseTextArea.prefWidthProperty().bind(widthProperty());
+ VBox.setVgrow(licenseTextArea, Priority.ALWAYS);
+
+ getChildren().addAll(label, licenseTextArea);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/FirstTimeUse/WelcomePane.java
@@ -0,0 +1,17 @@
+package com.StreamPi.Server.Window.FirstTimeUse;
+
+import javafx.scene.control.Label;
+import javafx.scene.layout.VBox;
+
+public class WelcomePane extends VBox{
+ public WelcomePane()
+ {
+ getStyleClass().add("first_time_use_pane_welcome");
+
+ Label label = new Label("Welcome to StreamPi!\nClick on 'Next' to continue with the setup.");
+
+ getChildren().add(label);
+
+ setVisible(false);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/FirstTimeUse/WindowName.java
@@ -0,0 +1,5 @@
+package com.StreamPi.Server.Window.FirstTimeUse;
+
+public enum WindowName {
+ WELCOME, LICENSE, FINAL
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Settings/About.java
@@ -0,0 +1,113 @@
+package com.StreamPi.Server.Window.Settings;
+
+import com.StreamPi.ActionAPI.ActionAPI;
+import com.StreamPi.Server.Info.License;
+import com.StreamPi.Server.Info.ServerInfo;
+import com.StreamPi.Server.Main;
+import com.StreamPi.Util.Platform.Platform;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import javafx.application.HostServices;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public class About extends VBox{
+
+ private HostServices hostServices;
+
+ public About(HostServices hostServices)
+ {
+
+ getStyleClass().add("about");
+ this.hostServices = hostServices;
+
+ setAlignment(Pos.TOP_CENTER);
+
+ Image appIcon = new Image(Main.class.getResourceAsStream("app_icon.png"));
+ ImageView appIconImageView = new ImageView(appIcon);
+ appIconImageView.setFitHeight(196);
+ appIconImageView.setFitWidth(182);
+
+ Label headerLabel = new Label("StreamPi");
+ headerLabel.getStyleClass().add("settings_about_streampi_header");
+
+ Label licenseLabel = new Label("License");
+ licenseLabel.getStyleClass().add("settings_about_license_label");
+
+ VBox.setMargin(licenseLabel, new Insets(20, 0 , 10 ,0));
+
+ TextArea licenseTextArea = new TextArea(License.getLicense());
+
+
+ licenseTextArea.setWrapText(false);
+ licenseTextArea.setEditable(false);
+ licenseTextArea.setMaxWidth(500);
+
+ VBox.setVgrow(licenseTextArea, Priority.ALWAYS);
+
+ HBox links = new HBox();
+
+ Hyperlink github = new Hyperlink("GitHub");
+ github.setOnAction(event -> {
+ openWebpage("https://github.com/Stream-Pi");
+ });
+
+ Hyperlink discord = new Hyperlink("Discord");
+ discord.setOnAction(event -> {
+ openWebpage("https://discord.gg/BExqGmk");
+ });
+
+ Hyperlink website = new Hyperlink("Website");
+ website.setOnAction(event -> {
+ openWebpage("https://stream-pi.com");
+ });
+
+ Hyperlink twitter = new Hyperlink("Twitter");
+ twitter.setOnAction(event -> {
+ openWebpage("https://twitter.com/Stream_Pi");
+ });
+
+ links.setSpacing(15);
+ links.setAlignment(Pos.CENTER);
+ links.getChildren().addAll(github, discord, website, twitter);
+
+ Hyperlink donateButton = new Hyperlink("DONATE");
+ donateButton.setOnAction(event -> {
+ openWebpage("https://www.patreon.com/streampi");
+ });
+ donateButton.getStyleClass().add("settings_about_donate_hyperlink");
+
+ Region gap = new Region();
+ VBox.setVgrow(gap, Priority.ALWAYS);
+
+ ServerInfo serverInfo = ServerInfo.getInstance();
+
+ Label versionText = new Label(serverInfo.getVersion().getText() + " - "+ serverInfo.getPlatformType().getUIName() + " - "+ serverInfo.getReleaseStatus().getUIName());
+ Label commAPILabel = new Label("CommAPI "+serverInfo.getCommAPIVersion().getText());
+ Label minThemeAPILabel = new Label("Min ThemeAPI "+serverInfo.getMinThemeSupportVersion().getText());
+ Label minActionAPILabel = new Label("Min ActionAPI "+serverInfo.getMinPluginSupportVersion().getText());
+
+
+ Label currentActionAPILabel = new Label("ActionAPI "+ ActionAPI.API_VERSION.getText());
+
+ setSpacing(3);
+
+ getChildren().addAll(appIconImageView, headerLabel, licenseLabel, licenseTextArea, links, donateButton, gap, versionText, commAPILabel, minThemeAPILabel, minActionAPILabel, currentActionAPILabel);
+ }
+
+ public void openWebpage(String url) {
+ hostServices.showDocument(url);
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Settings/ClientsSettings.java
@@ -0,0 +1,689 @@
+package com.StreamPi.Server.Window.Settings;
+
+import com.StreamPi.Server.Client.Client;
+import com.StreamPi.Server.Client.ClientProfile;
+import com.StreamPi.Server.Client.ClientTheme;
+import com.StreamPi.Server.Connection.ClientConnection;
+import com.StreamPi.Server.Connection.ClientConnections;
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.FormHelper.HBoxInputBox;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import com.StreamPi.Util.Platform.ReleaseStatus;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.concurrent.Task;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.util.Callback;
+import org.w3c.dom.Text;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class ClientsSettings extends VBox {
+ private VBox clientsSettingsVBox;
+ private Button saveButton;
+
+ private ServerListener serverListener;
+
+ private Logger logger;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ public ClientsSettings(ExceptionAndAlertHandler exceptionAndAlertHandler, ServerListener serverListener)
+ {
+ getStyleClass().add("clients_settings");
+ this.serverListener = serverListener;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+
+ clientSettingsVBoxArrayList = new ArrayList<>();
+
+ setPadding(new Insets(10.0));
+
+ logger = Logger.getLogger(ClientsSettings.class.getName());
+
+ clientsSettingsVBox = new VBox();
+ clientsSettingsVBox.setSpacing(20.0);
+ clientsSettingsVBox.setAlignment(Pos.TOP_CENTER);
+
+ setAlignment(Pos.TOP_CENTER);
+
+ ScrollPane scrollPane = new ScrollPane();
+ scrollPane.getStyleClass().add("clients_settings_scroll_pane");
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
+ VBox.setVgrow(scrollPane, Priority.ALWAYS);
+ scrollPane.maxWidthProperty().bind(widthProperty().multiply(0.8));
+
+ clientsSettingsVBox.prefWidthProperty().bind(scrollPane.widthProperty().subtract(25));
+ scrollPane.setContent(clientsSettingsVBox);
+
+ saveButton = new Button("Save");
+ saveButton.setOnAction(event -> onSaveButtonClicked());
+
+ HBox hBox = new HBox(saveButton);
+ hBox.setAlignment(Pos.CENTER_RIGHT);
+
+ getChildren().addAll(scrollPane, hBox);
+
+ setCache(true);
+ setCacheHint(CacheHint.SPEED);
+ }
+
+ public void onSaveButtonClicked()
+ {
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call()
+ {
+ try
+ {
+ Platform.runLater(()->saveButton.setDisable(true));
+ StringBuilder finalErrors = new StringBuilder();
+
+ for(ClientSettingsVBox clientSettingsVBox : clientSettingsVBoxArrayList)
+ {
+ StringBuilder errors = new StringBuilder();
+
+ if(clientSettingsVBox.getNickname().isBlank())
+ errors.append(" Cannot have blank nickname. \n");
+
+ try {
+ Double.parseDouble(clientSettingsVBox.getStartupWindowHeight());
+ }
+ catch (NumberFormatException e)
+ {
+ errors.append(" Must have integer display height. \n");
+ }
+
+ try {
+ Double.parseDouble(clientSettingsVBox.getStartupWindowWidth());
+ }
+ catch (NumberFormatException e)
+ {
+ errors.append(" Must have integer display width. \n");
+ }
+
+ for(ClientProfileVBox clientProfileVBox : clientSettingsVBox.getClientProfileVBoxes())
+ {
+ StringBuilder errors2 = new StringBuilder();
+
+ if(clientProfileVBox.getName().isBlank())
+ errors2.append(" cannot have blank nickname. \n");
+
+ try {
+ Integer.parseInt(clientProfileVBox.getActionSize());
+ }
+ catch (NumberFormatException e)
+ {
+ errors2.append(" Must have integer Action Size. \n");
+ }
+
+
+ try {
+ Integer.parseInt(clientProfileVBox.getActionGap());
+ }
+ catch (NumberFormatException e)
+ {
+ errors2.append(" Must have integer Action Gap. \n");
+ }
+
+
+ try {
+ Integer.parseInt(clientProfileVBox.getRows());
+ }
+ catch (NumberFormatException e)
+ {
+ errors2.append(" Must have integer Rows. \n");
+ }
+
+
+ try {
+ Integer.parseInt(clientProfileVBox.getCols());
+ }
+ catch (NumberFormatException e)
+ {
+ errors2.append(" Must have integer Columns. \n");
+ }
+
+
+ if(!errors2.toString().isEmpty())
+ {
+ errors.append(" ")
+ .append(clientProfileVBox.getRealName())
+ .append("\n")
+ .append(errors2.toString())
+ .append("\n");
+ }
+ }
+
+
+ if(!errors.toString().isEmpty())
+ {
+ finalErrors.append("* ")
+ .append(clientSettingsVBox.getRealNickName())
+ .append("\n")
+ .append(errors.toString())
+ .append("\n");
+ }
+
+
+
+ }
+
+ if(!finalErrors.toString().isEmpty())
+ throw new MinorException("You made form mistakes",
+ "Please fix the following issues : \n"+finalErrors.toString());
+
+
+
+ //save details and values
+ for(ClientSettingsVBox clientSettingsVBox : clientSettingsVBoxArrayList)
+ {
+ clientSettingsVBox.saveClientAndProfileDetails();
+ }
+
+ loadData();
+ serverListener.clearTemp();
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ catch (SevereException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ catch (CloneNotSupportedException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleSevereException(new SevereException(
+ e.getMessage()
+ ));
+ }
+ finally
+ {
+ Platform.runLater(()->saveButton.setDisable(false));
+ }
+ return null;
+ }
+ }).start();
+ }
+
+ private ArrayList<ClientSettingsVBox> clientSettingsVBoxArrayList;
+
+ public void loadData()
+ {
+ logger.info("Loading client data into ClientsSettings ...");
+
+ Platform.runLater(()-> clientsSettingsVBox.getChildren().clear());
+ clientSettingsVBoxArrayList.clear();
+
+ List<ClientConnection> clientConnections = ClientConnections.getInstance().getConnections();
+
+ if(clientConnections.size() == 0)
+ {
+ Platform.runLater(()->{
+ clientsSettingsVBox.getChildren().add(new Label("No Clients Connected."));
+ saveButton.setVisible(false);
+ });
+ }
+ else
+ {
+ Platform.runLater(()->saveButton.setVisible(true));
+ for (ClientConnection clientConnection : clientConnections) {
+ ClientSettingsVBox clientSettingsVBox = new ClientSettingsVBox(clientConnection);
+
+ clientSettingsVBoxArrayList.add(clientSettingsVBox);
+ Platform.runLater(()->clientsSettingsVBox.getChildren().add(clientSettingsVBox));
+ }
+ }
+
+ logger.info("... Done!");
+ }
+
+ public class ClientSettingsVBox extends VBox
+ {
+ private ComboBox<ClientProfile> profilesComboBox;
+
+ private ComboBox<ClientTheme> themesComboBox;
+
+ private TextField startupWindowHeightTextField;
+
+ public String getStartupWindowHeight() {
+ return startupWindowHeightTextField.getText();
+ }
+
+ private TextField startupWindowWidthTextField;
+
+ public String getStartupWindowWidth() {
+ return startupWindowWidthTextField.getText();
+ }
+
+ private TextField nicknameTextField;
+
+ public String getNickname() {
+ return nicknameTextField.getText();
+ }
+
+ private Label nickNameLabel;
+
+ private Label versionLabel;
+
+ public String getRealNickName()
+ {
+ return nickNameLabel.getText();
+ }
+
+ private Label socketConnectionLabel;
+
+ private ClientConnection connection;
+
+ private Accordion profilesAccordion;
+
+ private ArrayList<ClientProfileVBox> clientProfileVBoxes;
+
+ private Label platformLabel;
+
+ private HBoxInputBox startupWindowHeightInputBox, startupWindowWidthInputBox;
+
+ public ArrayList<ClientProfileVBox> getClientProfileVBoxes() {
+ return clientProfileVBoxes;
+ }
+
+ public ClientSettingsVBox(ClientConnection connection)
+ {
+ this.connection = connection;
+
+ clientProfileVBoxes = new ArrayList<>();
+
+ initUI();
+ loadValues();
+ }
+
+ public ClientConnection getConnection()
+ {
+ return connection;
+ }
+
+ public void saveClientAndProfileDetails() throws SevereException, CloneNotSupportedException, MinorException {
+ System.out.println("IIN");
+ getConnection().saveClientDetails(
+ nicknameTextField.getText(),
+ startupWindowWidthTextField.getText(),
+ startupWindowHeightTextField.getText(),
+ profilesComboBox.getSelectionModel().getSelectedItem().getID(),
+ themesComboBox.getSelectionModel().getSelectedItem().getThemeFullName()
+ );
+
+ System.out.println("OUT");
+
+ logger.info("Profiles : ");
+ for(ClientProfileVBox clientProfileVBox : clientProfileVBoxes)
+ {
+ logger.info("Name : "+clientProfileVBox.getClientProfile().getName());
+ getConnection().saveProfileDetails(clientProfileVBox.getClientProfile());
+ }
+
+
+ //remove deleted client profiles
+ for(ClientProfile clientProfile : connection.getClient().getAllClientProfiles())
+ {
+ boolean found = false;
+ for(ClientProfileVBox clientProfileVBox : clientProfileVBoxes)
+ {
+ if(clientProfileVBox.getClientProfile().getID().equals(clientProfile.getID()))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found)
+ {
+ connection.getClient().removeProfileFromID(clientProfile.getID());
+ connection.deleteProfile(clientProfile.getID());
+ }
+ }
+
+
+ }
+
+ public void initUI()
+ {
+ profilesComboBox = new ComboBox<>();
+ Callback<ListView<ClientProfile>, ListCell<ClientProfile>> profilesComboBoxFactory = new Callback<>() {
+ @Override
+ public ListCell<ClientProfile> call(ListView<ClientProfile> clientConnectionListView) {
+
+ return new ListCell<>() {
+ @Override
+ protected void updateItem(ClientProfile clientProfile, boolean b) {
+ super.updateItem(clientProfile, b);
+
+ if(clientProfile == null)
+ {
+ setText(null);
+ }
+ else
+ {
+ setText(clientProfile.getName());
+ }
+ }
+ };
+ }
+ };
+ profilesComboBox.setCellFactory(profilesComboBoxFactory);
+ profilesComboBox.setButtonCell(profilesComboBoxFactory.call(null));
+
+ themesComboBox = new ComboBox<>();
+ Callback<ListView<ClientTheme>, ListCell<ClientTheme>> themesComboBoxFactory = new Callback<>() {
+ @Override
+ public ListCell<ClientTheme> call(ListView<ClientTheme> clientConnectionListView) {
+
+ return new ListCell<>() {
+ @Override
+ protected void updateItem(ClientTheme clientTheme, boolean b) {
+ super.updateItem(clientTheme, b);
+
+ if(clientTheme == null)
+ {
+ setText(null);
+ }
+ else
+ {
+ setText(clientTheme.getShortName());
+ }
+ }
+ };
+ }
+ };
+ themesComboBox.setCellFactory(themesComboBoxFactory);
+ themesComboBox.setButtonCell(themesComboBoxFactory.call(null));
+
+ startupWindowHeightTextField = new TextField();
+ startupWindowWidthTextField = new TextField();
+
+ platformLabel = new Label();
+ platformLabel.getStyleClass().add("settings_client_platform_label");
+
+ socketConnectionLabel = new Label();
+ socketConnectionLabel.getStyleClass().add("settings_client_socket_connection_label");
+
+ nicknameTextField = new TextField();
+
+ nickNameLabel = new Label();
+ nickNameLabel.getStyleClass().add("settings_client_nick_name_label");
+
+ versionLabel = new Label();
+ versionLabel.getStyleClass().add("settings_client_version_label");
+
+ profilesAccordion = new Accordion();
+ VBox.setMargin(profilesAccordion, new Insets(0,0,20,0));
+
+
+ Button addNewProfileButton = new Button("Add new Profile");
+ addNewProfileButton.setOnAction(event -> onNewProfileButtonClicked());
+
+ setSpacing(10.0);
+
+ getStyleClass().add("settings_clients_each_client");
+
+
+ startupWindowHeightInputBox = new HBoxInputBox("Startup Window Height", startupWindowHeightTextField);
+ startupWindowHeightInputBox.managedProperty().bind(startupWindowHeightInputBox.visibleProperty());
+
+ startupWindowWidthInputBox = new HBoxInputBox("Startup Window Width", startupWindowWidthTextField);
+ startupWindowWidthInputBox.managedProperty().bind(startupWindowWidthInputBox.visibleProperty());
+
+
+ this.getChildren().addAll(
+ nickNameLabel,
+ socketConnectionLabel,
+ platformLabel,
+ versionLabel,
+ new HBoxInputBox("Nickname",nicknameTextField),
+ new HBox(
+ new Label("Theme"),
+ new SpaceFiller(SpaceFiller.FillerType.HBox),
+ themesComboBox
+ ),
+
+ startupWindowHeightInputBox,
+
+ startupWindowWidthInputBox,
+
+ new HBox(new Label("Startup Profile"),
+ new SpaceFiller(SpaceFiller.FillerType.HBox),
+ profilesComboBox),
+
+ addNewProfileButton,
+
+ profilesAccordion);
+ }
+
+ public void loadValues()
+ {
+ Client client = connection.getClient();
+
+ profilesComboBox.setItems(FXCollections.observableList(client.getAllClientProfiles()));
+ profilesComboBox.getSelectionModel().select(
+ client.getProfileByID(client.getDefaultProfileID())
+ );
+
+
+ themesComboBox.setItems(FXCollections.observableList(client.getThemes()));
+ themesComboBox.getSelectionModel().select(
+ client.getThemeByFullName(
+ client.getDefaultThemeFullName()
+ )
+ );
+
+ nicknameTextField.setText(client.getNickName());
+
+ if(client.getPlatform() == com.StreamPi.Util.Platform.Platform.ANDROID)
+ {
+ startupWindowHeightInputBox.setVisible(false);
+ startupWindowWidthInputBox.setVisible(false);
+ }
+
+ platformLabel.setText("Platform : "+client.getPlatform().getUIName());
+
+ startupWindowWidthTextField.setText(client.getStartupDisplayWidth()+"");
+ startupWindowHeightTextField.setText(client.getStartupDisplayHeight()+"");
+
+ socketConnectionLabel.setText(client.getRemoteSocketAddress().toString().substring(1)); //substring removes the `/`
+
+ nickNameLabel.setText(client.getNickName());
+
+ versionLabel.setText(client.getReleaseStatus().getUIName()+" "+client.getVersion().getText());
+
+ //add profiles
+ for(ClientProfile clientProfile : client.getAllClientProfiles())
+ {
+ TitledPane titledPane = new TitledPane();
+ titledPane.setText(clientProfile.getName());
+
+ ClientProfileVBox clientProfileVBox = new ClientProfileVBox(clientProfile);
+
+ clientProfileVBox.getRemoveButton().setOnAction(event -> onProfileDeleteButtonClicked(clientProfileVBox, titledPane));
+
+ titledPane.setContent(clientProfileVBox);
+
+ clientProfileVBoxes.add(clientProfileVBox);
+
+ profilesAccordion.getPanes().add(titledPane);
+ }
+ }
+
+ public void onNewProfileButtonClicked()
+ {
+ ClientProfile clientProfile = new ClientProfile(
+ "Untitled Profile",
+ 3,
+ 3,
+ 100,
+ 5
+ );
+
+
+ ClientProfileVBox clientProfileVBox = new ClientProfileVBox(clientProfile);
+ TitledPane titledPane = new TitledPane();
+ titledPane.setContent(clientProfileVBox);
+ titledPane.setText(clientProfile.getName());
+
+ clientProfileVBox.getRemoveButton().setOnAction(event -> onProfileDeleteButtonClicked(clientProfileVBox, titledPane));
+
+ clientProfileVBoxes.add(clientProfileVBox);
+
+ profilesAccordion.getPanes().add(titledPane);
+ }
+
+ public void onProfileDeleteButtonClicked(ClientProfileVBox clientProfileVBox, TitledPane titledPane)
+ {
+ if(clientProfileVBoxes.size() == 1)
+ {
+ exceptionAndAlertHandler.handleMinorException(new MinorException("Only one",
+ "You cannot delete all profiles"));
+ }
+ else
+ {
+ if(profilesComboBox.getSelectionModel().getSelectedItem().getID().equals(clientProfileVBox.getClientProfile().getID()))
+ {
+ exceptionAndAlertHandler.handleMinorException(new MinorException("Default",
+ "You cannot delete default profile. Change to another one to delete this."));
+ }
+ else
+ {
+ clientProfileVBoxes.remove(clientProfileVBox);
+ profilesComboBox.getItems().remove(clientProfileVBox.getClientProfile());
+
+ profilesAccordion.getPanes().remove(titledPane);
+ }
+ }
+ }
+ }
+
+ public class ClientProfileVBox extends VBox
+ {
+ private TextField nameTextField;
+
+ public String getName()
+ {
+ return nameTextField.getText();
+ }
+
+ private TextField rowsTextField;
+
+ public String getRows()
+ {
+ return rowsTextField.getText();
+ }
+
+ private TextField colsTextField;
+
+ public String getCols()
+ {
+ return colsTextField.getText();
+ }
+
+ private TextField actionSizeTextField;
+
+ public String getActionSize()
+ {
+ return actionSizeTextField.getText();
+ }
+
+ private TextField actionGapTextField;
+
+ public String getActionGap()
+ {
+ return actionGapTextField.getText();
+ }
+
+ private Button removeButton;
+
+ private ClientProfile clientProfile;
+
+ public String getRealName()
+ {
+ return clientProfile.getName();
+ }
+
+ public ClientProfileVBox(ClientProfile clientProfile)
+ {
+ this.clientProfile = clientProfile;
+
+ initUI();
+ loadValues(clientProfile);
+ }
+
+ public void initUI()
+ {
+ setPadding(new Insets(5.0));
+ setSpacing(10.0);
+
+ nameTextField = new TextField();
+ rowsTextField = new TextField();
+ colsTextField = new TextField();
+ actionSizeTextField = new TextField();
+ actionGapTextField = new TextField();
+
+ removeButton = new Button("Remove");
+
+ HBox hBox = new HBox(removeButton);
+ hBox.setAlignment(Pos.CENTER_RIGHT);
+
+
+ getChildren().addAll(
+ new HBoxInputBox("Name ", nameTextField),
+ new HBoxInputBox("Columns", rowsTextField),
+ new HBoxInputBox("Rows", colsTextField),
+ new HBoxInputBox("Action Size", actionSizeTextField),
+ new HBoxInputBox("Action Gap", actionGapTextField),
+ hBox
+ );
+ }
+
+ public Button getRemoveButton()
+ {
+ return removeButton;
+ }
+
+ public void loadValues(ClientProfile clientProfile)
+ {
+ nameTextField.setText(clientProfile.getName());
+
+ rowsTextField.setText(clientProfile.getRows()+"");
+ colsTextField.setText(clientProfile.getCols()+"");
+
+ actionSizeTextField.setText(clientProfile.getActionSize()+"");
+ actionGapTextField.setText(clientProfile.getActionGap()+"");
+ }
+
+ public ClientProfile getClientProfile()
+ {
+ clientProfile.setActionGap(Integer.parseInt(actionGapTextField.getText()));
+ clientProfile.setActionSize(Integer.parseInt(actionSizeTextField.getText()));
+ clientProfile.setRows(Integer.parseInt(rowsTextField.getText()));
+ clientProfile.setCols(Integer.parseInt(colsTextField.getText()));
+ clientProfile.setName(nameTextField.getText());
+
+ return clientProfile;
+ }
+ }
+
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Settings/GeneralSettings.java
@@ -0,0 +1,492 @@
+package com.StreamPi.Server.Window.Settings;
+
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.IO.Config;
+import com.StreamPi.Server.Controller.Controller;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.Util.Alert.StreamPiAlert;
+import com.StreamPi.Util.Alert.StreamPiAlertType;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import com.StreamPi.Util.StartAtBoot.SoftwareType;
+import com.StreamPi.Util.StartAtBoot.StartAtBoot;
+import com.StreamPi.Util.Version.Version;
+import com.StreamPi.Server.Info.ServerInfo;
+
+import javafx.application.HostServices;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.*;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.*;
+import javafx.scene.paint.Paint;
+import javafx.stage.DirectoryChooser;
+import javafx.stage.Stage;
+import org.json.JSONObject;
+import org.kordamp.ikonli.javafx.FontIcon;
+import org.w3c.dom.Text;
+import java.awt.SystemTray;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.awt.PopupMenu;
+import java.awt.MenuItem;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.logging.Logger;
+
+public class GeneralSettings extends ScrollPane {
+
+ private final TextField serverNameTextField;
+ private final TextField portTextField;
+ private final TextField pluginsPathTextField;
+ private final TextField themesPathTextField;
+ private final TextField actionGridPaneActionBoxSize;
+ private final TextField actionGridPaneActionBoxGap;
+ private final ToggleButton startOnBootToggleButton;
+ private final ToggleButton closeOnXToggleButton;
+ private final Button saveButton;
+ private final Button checkForUpdatesButton;
+
+
+ private Logger logger;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private ServerListener serverListener;
+
+ private HostServices hostServices;
+
+ public GeneralSettings(ExceptionAndAlertHandler exceptionAndAlertHandler, ServerListener serverListener, HostServices hostServices)
+ {
+ this.hostServices = hostServices;
+
+ getStyleClass().add("general_settings_scroll_pane");
+
+
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.serverListener = serverListener;
+
+ logger = Logger.getLogger(GeneralSettings.class.getName());
+
+ setHbarPolicy(ScrollBarPolicy.NEVER);
+
+ serverNameTextField = new TextField();
+
+ portTextField = new TextField();
+
+ pluginsPathTextField = new TextField();
+
+ themesPathTextField = new TextField();
+
+ actionGridPaneActionBoxSize = new TextField();
+ actionGridPaneActionBoxGap = new TextField();
+
+ startOnBootToggleButton = new ToggleButton("Start on Boot");
+ closeOnXToggleButton = new ToggleButton("Quit On Window Close");
+
+ checkForUpdatesButton = new Button("Check for updates");
+ checkForUpdatesButton.setOnAction(event->checkForUpdates());
+
+
+ VBox vBox = new VBox();
+ vBox.prefWidthProperty().bind(widthProperty());
+ vBox.setAlignment(Pos.CENTER);
+ vBox.setSpacing(5);
+
+ vBox.getChildren().addAll(
+ getUIInputBox("Server Name", serverNameTextField),
+ getUIInputBox("Port", portTextField),
+ getUIInputBox("Action Grid Pane Action Box Size", actionGridPaneActionBoxSize),
+ getUIInputBox("Action Grid Pane Action Box Gap", actionGridPaneActionBoxGap),
+ getUIInputBoxWithDirectoryChooser("Plugins Path", pluginsPathTextField),
+ getUIInputBoxWithDirectoryChooser("Themes Path", themesPathTextField)
+ );
+
+ serverNameTextField.setPrefWidth(200);
+
+ HBox toggleButtons = new HBox(closeOnXToggleButton, startOnBootToggleButton);
+ toggleButtons.setSpacing(10.0);
+ VBox.setMargin(toggleButtons, new Insets(30, 0 , 0,0));
+ toggleButtons.setAlignment(Pos.CENTER);
+
+ saveButton = new Button("Save");
+ saveButton.setOnAction(event->save());
+
+ vBox.getChildren().addAll(toggleButtons, checkForUpdatesButton, saveButton);
+
+ vBox.setPadding(new Insets(10));
+
+
+ vBox.getStyleClass().add("general_settings");
+ setContent(vBox);
+ }
+
+ private HBox getUIInputBoxWithDirectoryChooser(String labelText, TextField textField)
+ {
+ HBox hBox = getUIInputBox(labelText, textField);
+ hBox.setSpacing(5.0);
+
+ TextField tf = (TextField) hBox.getChildren().get(2);
+ tf.setPrefWidth(300);
+ tf.setDisable(true);
+
+
+ Button button = new Button();
+ FontIcon fontIcon = new FontIcon("far-folder");
+ button.setGraphic(fontIcon);
+
+ button.setOnAction(event -> {
+ DirectoryChooser directoryChooser = new DirectoryChooser();
+
+
+ try {
+ File selectedDirectory = directoryChooser.showDialog(getScene().getWindow());
+
+ textField.setText(selectedDirectory.getAbsolutePath());
+ }
+ catch (NullPointerException e)
+ {
+ logger.info("No folder selected");
+ }
+ });
+
+ hBox.getChildren().add(button);
+
+
+ return hBox;
+ }
+
+ private HBox getUIInputBox(String labelText, TextField textField)
+ {
+ textField.setPrefWidth(100);
+
+ Label label = new Label(labelText);
+ Region region = new Region();
+ HBox.setHgrow(region, Priority.ALWAYS);
+
+
+ return new HBox(label, region, textField);
+ }
+
+
+
+ public void loadDataFromConfig() throws SevereException {
+ Config config = Config.getInstance();
+
+ Platform.runLater(()->
+ {
+ serverNameTextField.setText(config.getServerName());
+ portTextField.setText(config.getPort()+"");
+ pluginsPathTextField.setText(config.getPluginsPath());
+ themesPathTextField.setText(config.getThemesPath());
+ actionGridPaneActionBoxSize.setText(config.getActionGridActionSize()+"");
+ actionGridPaneActionBoxGap.setText(config.getActionGridActionGap()+"");
+
+ closeOnXToggleButton.setSelected(config.getCloseOnX());
+ startOnBootToggleButton.setSelected(config.getStartOnBoot());
+ });
+ }
+
+ public void save()
+ {
+ new Thread(new Task<Void>() {
+ @Override
+ protected Void call()
+ {
+ try {
+ boolean toBeReloaded = false;
+ boolean dashToBeReRendered = false;
+
+ Platform.runLater(()->{
+ saveButton.setDisable(true);
+
+ serverNameTextField.setDisable(true);
+ portTextField.setDisable(true);
+
+ closeOnXToggleButton.setDisable(true);
+ startOnBootToggleButton.setDisable(true);
+ });
+
+ String serverNameStr = serverNameTextField.getText();
+ String serverPortStr = portTextField.getText();
+ String pluginsPathStr = pluginsPathTextField.getText();
+ String themesPathStr = themesPathTextField.getText();
+
+ String actionGridActionBoxSize = actionGridPaneActionBoxSize.getText();
+ String actionGridActionBoxGap = actionGridPaneActionBoxGap.getText();
+
+ boolean closeOnX = closeOnXToggleButton.isSelected();
+ boolean startOnBoot = startOnBootToggleButton.isSelected();
+
+ Config config = Config.getInstance();
+
+ StringBuilder errors = new StringBuilder();
+
+
+ if(serverNameStr.isBlank())
+ {
+ errors.append("* Server Name cannot be blank.\n");
+ }
+ else
+ {
+ if(!config.getServerName().equals(serverNameStr))
+ {
+ toBeReloaded = true;
+ }
+ }
+
+
+ int serverPort=-1;
+ try {
+ serverPort = Integer.parseInt(serverPortStr);
+
+ if (serverPort < 1024)
+ errors.append("* Server Port must be more than 1024");
+
+ if(config.getPort()!=serverPort)
+ {
+ toBeReloaded = true;
+ }
+ }
+ catch (NumberFormatException e)
+ {
+ errors.append("* Server Port must be integer.\n");
+ }
+
+
+ int actionSize=-1;
+ try {
+ actionSize = Integer.parseInt(actionGridActionBoxSize);
+
+ if(config.getActionGridActionSize() != actionSize)
+ {
+ dashToBeReRendered = true;
+ }
+ }
+ catch (NumberFormatException e)
+ {
+ errors.append("* Action Size must be integer.\n");
+ }
+
+
+ int actionGap=-1;
+ try {
+ actionGap = Integer.parseInt(actionGridActionBoxGap);
+
+ if(config.getActionGridActionGap() != actionGap)
+ {
+ dashToBeReRendered = true;
+ }
+ }
+ catch (NumberFormatException e)
+ {
+ errors.append("* Action Gap must be integer.\n");
+ }
+
+ if(pluginsPathStr.isBlank())
+ {
+ errors.append("* Plugins Path must not be blank.\n");
+ }
+ else
+ {
+ if(!config.getPluginsPath().equals(pluginsPathStr))
+ {
+ toBeReloaded = true;
+ }
+ }
+
+ if(themesPathStr.isBlank())
+ {
+ errors.append("* Themes Path must not be blank.\n");
+ }
+ else
+ {
+ if(!config.getThemesPath().equals(themesPathStr))
+ {
+ toBeReloaded = true;
+ }
+ }
+
+ if(!errors.toString().isEmpty())
+ {
+ throw new MinorException("Settings", "Please rectify the following errors and try again :\n"+errors.toString());
+ }
+
+ if(config.getStartOnBoot() != startOnBoot)
+ {
+ if(ServerInfo.getInstance().getRunnerFileName() == null)
+ {
+ new StreamPiAlert("Uh Oh", "No Runner File Name Specified as startup arguments. Cant set run at boot.", StreamPiAlertType.ERROR).show();
+ startOnBoot = false;
+ }
+ else
+ {
+ StartAtBoot startAtBoot = new StartAtBoot(SoftwareType.SERVER, ServerInfo.getInstance().getPlatformType());
+ if(startOnBoot)
+ {
+ startAtBoot.create(new File(ServerInfo.getInstance().getRunnerFileName()));
+ }
+ else
+ {
+ boolean result = startAtBoot.delete();
+ if(!result)
+ new StreamPiAlert("Uh Oh!", "Unable to delete starter file", StreamPiAlertType.ERROR).show();
+ }
+ }
+ }
+
+ if(!closeOnX)
+ {
+ System.out.println("XYZ");
+ if(!SystemTray.isSupported())
+ {
+ StreamPiAlert alert = new StreamPiAlert("Not Supported", "Tray System not supported on this platform ", StreamPiAlertType.ERROR);
+ alert.show();
+
+ closeOnX = true;
+ }
+ else
+ {
+ System.out.println("YES");
+ }
+ }
+
+ config.setServerName(serverNameStr);
+ config.setServerPort(serverPort);
+ config.setActionGridGap(actionGap);
+ config.setActionGridSize(actionSize);
+ config.setPluginsPath(pluginsPathStr);
+ config.setThemesPath(themesPathStr);
+
+ config.setCloseOnX(closeOnX);
+ config.setStartupOnBoot(startOnBoot);
+
+ config.save();
+
+
+ loadDataFromConfig();
+
+ if(toBeReloaded)
+ {
+ new StreamPiAlert("Restart","Restart to see changes", StreamPiAlertType.INFORMATION).show();
+ }
+
+ if(dashToBeReRendered)
+ {
+ serverListener.clearTemp();
+ }
+ }
+ catch (MinorException e)
+ {
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ catch (SevereException e)
+ {
+ exceptionAndAlertHandler.handleSevereException(e);
+ }
+ finally {
+ Platform.runLater(()->{
+ saveButton.setDisable(false);
+
+ serverNameTextField.setDisable(false);
+ portTextField.setDisable(false);
+
+ closeOnXToggleButton.setDisable(false);
+ startOnBootToggleButton.setDisable(false);
+ });
+ }
+ return null;
+ }
+ }).start();
+ }
+
+ public void checkForUpdates()
+ {
+ new Thread(new Task<Void>()
+ {
+ @Override
+ protected Void call() throws Exception {
+ try
+ {
+ Platform.runLater(()->checkForUpdatesButton.setDisable(true));
+
+
+ String jsonRaw = readUrl("https://stream-pi.com/API/get_latest.php?TYPE=SERVER");
+
+ System.out.println(jsonRaw);
+
+ JSONObject jsonObject = new JSONObject(jsonRaw);
+
+ String latestVersionRaw = jsonObject.getString("Version");
+ String releasePage = jsonObject.getString("Release Page");
+
+ Version latestVersion = new Version(latestVersionRaw);
+ Version currentVersion = ServerInfo.getInstance().getVersion();
+
+ if(latestVersion.isBiggerThan(currentVersion))
+ {
+ VBox vBox = new VBox();
+
+ Hyperlink urlLabel = new Hyperlink(releasePage);
+ urlLabel.setOnAction(event->hostServices.showDocument(releasePage));
+
+ Label label = new Label(
+ "New Version "+latestVersionRaw+" Available.\n" +
+ "Current Version "+currentVersion.getText()+".\n"+
+ "Changelog and install instructions are included in the release page.\n" +
+ "It is recommended to update to ensure maximum stability and least bugs.");
+ label.setWrapText(true);
+
+ vBox.setSpacing(5);
+ vBox.getChildren().addAll(
+ urlLabel,
+ label
+ );
+
+ new StreamPiAlert("New Update Available!", StreamPiAlertType.INFORMATION, vBox).show();;
+ }
+ else
+ {
+ new StreamPiAlert("Up to Date", "Server is upto date. ("+currentVersion.getText()+")", StreamPiAlertType.INFORMATION).show();;
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ new StreamPiAlert("Uh Oh", "Update Check Failed. API Error/Network issue.", StreamPiAlertType.WARNING).show();;
+ }
+ finally
+ {
+ Platform.runLater(()->checkForUpdatesButton.setDisable(false));
+ }
+ return null;
+ }
+ }).start();;
+ }
+
+ private String readUrl(String urlString) throws Exception {
+ BufferedReader reader = null;
+ try {
+ URL url = new URL(urlString);
+ reader = new BufferedReader(new InputStreamReader(url.openStream()));
+ StringBuffer buffer = new StringBuffer();
+ int read;
+ char[] chars = new char[1024];
+ while ((read = reader.read(chars)) != -1)
+ buffer.append(chars, 0, read);
+
+ return buffer.toString();
+ } finally {
+ if (reader != null)
+ reader.close();
+ }
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Settings/PluginsSettings.java
@@ -0,0 +1,377 @@
+package com.StreamPi.Server.Window.Settings;
+
+import com.StreamPi.Server.UIPropertyBox.UIPropertyBox;
+import com.StreamPi.ActionAPI.ActionProperty.Property.ControlType;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Type;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.Server.Action.NormalActionPlugins;
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.FormHelper.SpaceFiller;
+import com.StreamPi.Util.FormHelper.SpaceFiller.FillerType;
+
+import org.kordamp.ikonli.javafx.FontIcon;
+
+import javafx.application.HostServices;
+import javafx.application.Platform;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class PluginsSettings extends VBox {
+
+ private VBox pluginsSettingsVBox;
+
+ private ServerListener serverListener;
+
+ private Logger logger;
+
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ private HostServices hostServices;
+
+ public PluginsSettings(ExceptionAndAlertHandler exceptionAndAlertHandler, HostServices hostServices)
+ {
+ getStyleClass().add("plugins_settings");
+
+ this.hostServices = hostServices;
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ pluginProperties = new ArrayList<>();
+ logger = Logger.getLogger(PluginsSettings.class.getName());
+
+ setPadding(new Insets(10));
+
+ pluginsSettingsVBox = new VBox();
+ pluginsSettingsVBox.setSpacing(10.0);
+ pluginsSettingsVBox.setAlignment(Pos.TOP_CENTER);
+
+ ScrollPane scrollPane = new ScrollPane();
+
+ scrollPane.getStyleClass().add("plugins_settings_scroll_pane");
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+ scrollPane.maxWidthProperty().bind(widthProperty().multiply(0.8));
+
+ pluginsSettingsVBox.prefWidthProperty().bind(scrollPane.widthProperty().subtract(25));
+ scrollPane.setContent(pluginsSettingsVBox);
+
+ VBox.setVgrow(scrollPane, Priority.ALWAYS);
+
+ setAlignment(Pos.TOP_CENTER);
+
+
+ saveButton = new Button("Save");
+
+ saveButton.setOnAction(event -> onSaveButtonClicked());
+
+
+ HBox hBox = new HBox(saveButton);
+ hBox.setAlignment(Pos.CENTER_RIGHT);
+
+ getChildren().addAll(scrollPane, hBox);
+
+ }
+
+ private Button saveButton;
+
+ public void onSaveButtonClicked()
+ {
+ try {
+ //form validation
+ StringBuilder finalErrors = new StringBuilder();
+
+ for (PluginProperties p : pluginProperties)
+ {
+ StringBuilder errors = new StringBuilder();
+ for(int j = 0; j < p.getServerPropertyUIBox().size(); j++)
+ {
+ UIPropertyBox serverProperty = p.getServerPropertyUIBox().get(j);
+ Node controlNode = serverProperty.getControlNode();
+
+ if (serverProperty.getControlType() == ControlType.TEXT_FIELD)
+ {
+ String value = ((TextField) controlNode).getText();
+ if(serverProperty.getType() == Type.INTEGER)
+ {
+ try
+ {
+ Integer.parseInt(value);
+ }
+ catch (NumberFormatException e)
+ {
+ errors.append(" -> ").append(serverProperty.getDisplayName()).append(" must be integer.\n");
+ }
+ }
+ else
+ {
+ if(value.isBlank() && !serverProperty.isCanBeBlank())
+ errors.append(" -> ").append(serverProperty.getDisplayName()).append(" cannot be blank.\n");
+ }
+ }
+ }
+
+ if(!errors.toString().isBlank())
+ {
+ finalErrors.append(" * ").append(p.getName()).append("\n").append(errors.toString()).append("\n");
+ }
+ }
+
+ if(!finalErrors.toString().isEmpty())
+ {
+ throw new MinorException("Form Validation Errors",
+ "Please rectify the following errors and try again \n"+finalErrors.toString());
+ }
+
+ //save
+ for (PluginProperties pp : pluginProperties) {
+ for (int j = 0; j < pp.getServerPropertyUIBox().size(); j++) {
+
+
+ UIPropertyBox serverProperty = pp.getServerPropertyUIBox().get(j);
+
+ String rawValue = serverProperty.getRawValue();
+
+ NormalActionPlugins.getInstance().getActionFromIndex(pp.getIndex())
+ .getServerProperties().get()
+ .get(serverProperty.getIndex()).setRawValue(rawValue);
+ }
+ }
+
+
+ NormalActionPlugins.getInstance().saveServerSettings();
+
+ NormalActionPlugins.getInstance().initPlugins();
+ }
+ catch (MinorException e)
+ {
+ e.printStackTrace();
+ exceptionAndAlertHandler.handleMinorException(e);
+ }
+ }
+
+ private ArrayList<PluginProperties> pluginProperties;
+
+ public void loadPlugins() throws MinorException {
+
+ pluginProperties.clear();
+
+ List<NormalAction> actions = NormalActionPlugins.getInstance().getPlugins();
+
+ Platform.runLater(()-> pluginsSettingsVBox.getChildren().clear());
+
+ if(actions.size() == 0)
+ {
+ Platform.runLater(()->{
+ pluginsSettingsVBox.getChildren().add(new Label("No Plugins Installed."));
+ saveButton.setVisible(false);
+ });
+ return;
+ }
+ else
+ {
+ Platform.runLater(()->saveButton.setVisible(true));
+ }
+
+
+ for(int i = 0; i<actions.size(); i++)
+ {
+ NormalAction action = actions.get(i);
+
+ if(!action.isVisibleInServerSettingsPane())
+ continue;
+
+
+ Label headingLabel = new Label(action.getName());
+ headingLabel.getStyleClass().add("settings_plugins_each_action_heading");
+
+ HBox headerHBox = new HBox(headingLabel, new SpaceFiller(FillerType.HBox));
+
+
+ if (action.getRepo()!=null)
+ {
+ Button helpButton = new Button();
+ FontIcon questionIcon = new FontIcon("fas-question");
+ questionIcon.getStyleClass().add("dashboard_plugins_pane_action_help_icon");
+ helpButton.setGraphic(questionIcon);
+
+
+ helpButton.setOnAction(event -> {
+ hostServices.showDocument(action.getRepo());
+ });
+
+ headerHBox.getChildren().add(helpButton);
+ }
+
+
+
+ Label authorLabel = new Label(action.getAuthor());
+
+ Label moduleLabel = new Label(action.getModuleName());
+
+ Label versionLabel = new Label("Version : "+action.getVersion().getText());
+
+ VBox serverPropertiesVBox = new VBox();
+ serverPropertiesVBox.setSpacing(10.0);
+
+ List<Property> serverProperties = action.getServerProperties().get();
+
+ ArrayList<UIPropertyBox> serverPropertyArrayList = new ArrayList<>();
+
+
+ for(int j =0; j<serverProperties.size(); j++)
+ {
+ Property eachProperty = serverProperties.get(j);
+
+ if(!eachProperty.isVisible())
+ continue;
+
+
+ Label label = new Label(eachProperty.getDisplayName());
+
+ Region region = new Region();
+ HBox.setHgrow(region, Priority.ALWAYS);
+
+ HBox hBox = new HBox(label, new SpaceFiller(SpaceFiller.FillerType.HBox));
+ //hBox.setId(j+"");
+
+ Node controlNode = null;
+
+ if(eachProperty.getControlType() == ControlType.COMBO_BOX)
+ {
+ ComboBox<String> comboBox = new ComboBox<>();
+ comboBox.getItems().addAll(eachProperty.getListValue());
+ comboBox.getSelectionModel().select(eachProperty.getSelectedIndex());
+ hBox.getChildren().add(comboBox);
+
+ controlNode = comboBox;
+ }
+ else if(eachProperty.getControlType() == ControlType.TEXT_FIELD)
+ {
+ TextField textField = new TextField(eachProperty.getRawValue());
+
+ hBox.getChildren().add(textField);
+
+ controlNode = textField;
+ }
+ else if(eachProperty.getControlType() == ControlType.TOGGLE)
+ {
+ ToggleButton toggleButton = new ToggleButton();
+ toggleButton.setSelected(eachProperty.getBoolValue());
+
+ if(eachProperty.getBoolValue())
+ toggleButton.setText("ON");
+ else
+ toggleButton.setText("OFF");
+
+ toggleButton.selectedProperty().addListener((observableValue, aBoolean, t1) -> {
+ if(t1)
+ toggleButton.setText("ON");
+ else
+ toggleButton.setText("OFF");
+ });
+
+ hBox.getChildren().add(toggleButton);
+
+ controlNode = toggleButton;
+ }
+ else if(eachProperty.getControlType() == ControlType.SLIDER_DOUBLE)
+ {
+ Slider slider = new Slider();
+ slider.setValue(eachProperty.getDoubleValue());
+ slider.setMax(eachProperty.getMaxDoubleValue());
+ slider.setMin(eachProperty.getMinDoubleValue());
+
+ hBox.getChildren().add(slider);
+
+ controlNode = slider;
+ }
+ else if(eachProperty.getControlType() == ControlType.SLIDER_INTEGER)
+ {
+ Slider slider = new Slider();
+ slider.setValue(eachProperty.getIntValue());
+
+ slider.setMax(eachProperty.getMaxIntValue());
+ slider.setMin(eachProperty.getMinIntValue());
+ slider.setBlockIncrement(1.0);
+ slider.setSnapToTicks(true);
+
+ hBox.getChildren().add(slider);
+
+ controlNode = slider;
+ }
+
+
+ UIPropertyBox serverProperty = new UIPropertyBox(j, eachProperty.getDisplayName(), controlNode, eachProperty.getControlType(), eachProperty.getType(), eachProperty.isCanBeBlank());
+
+ serverPropertyArrayList.add(serverProperty);
+
+ serverPropertiesVBox.getChildren().add(hBox);
+
+ }
+
+ PluginProperties pp = new PluginProperties(i, serverPropertyArrayList, action.getName());
+
+ pluginProperties.add(pp);
+
+
+
+ Region region1 = new Region();
+ region1.setPrefHeight(5);
+
+
+ Platform.runLater(()->{
+ VBox vBox = new VBox();
+ vBox.setSpacing(5.0);
+ vBox.getChildren().addAll(headerHBox, authorLabel, moduleLabel, versionLabel, serverPropertiesVBox);
+
+ if(action.getButtonBar()!=null)
+ vBox.getChildren().add(new HBox(new SpaceFiller(SpaceFiller.FillerType.HBox), action.getButtonBar()));
+
+ vBox.getChildren().add(region1);
+ //vBox.setId(i+"");
+
+ vBox.getStyleClass().add("settings_plugins_each_action");
+
+ pluginsSettingsVBox.getChildren().add(vBox);
+
+ });
+ }
+ }
+
+ public class PluginProperties
+ {
+ private int index;
+ private ArrayList<UIPropertyBox> serverProperty;
+ private String name;
+
+ public PluginProperties(int index, ArrayList<UIPropertyBox> serverProperty, String name)
+ {
+ this.index = index;
+ this.serverProperty = serverProperty;
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public ArrayList<UIPropertyBox> getServerPropertyUIBox() {
+ return serverProperty;
+ }
+ }
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Settings/SettingsBase.java
@@ -0,0 +1,102 @@
+package com.StreamPi.Server.Window.Settings;
+
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.Window.ExceptionAndAlertHandler;
+import com.StreamPi.ThemeAPI.Theme;
+import javafx.application.HostServices;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.CacheHint;
+import javafx.scene.control.*;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+
+public class SettingsBase extends VBox {
+
+
+ private TabPane tabPane;
+
+ private GeneralSettings generalSettings;
+ private PluginsSettings pluginsSettings;
+ private ThemesSettings themesSettings;
+ private ClientsSettings clientsSettings;
+
+ private Button closeButton;
+
+ private HostServices hostServices;
+ private ExceptionAndAlertHandler exceptionAndAlertHandler;
+
+ public SettingsBase(HostServices hostServices, ExceptionAndAlertHandler exceptionAndAlertHandler,
+ ServerListener serverListener)
+ {
+ this.exceptionAndAlertHandler = exceptionAndAlertHandler;
+ this.hostServices = hostServices;
+
+ tabPane = new TabPane();
+ tabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
+ VBox.setVgrow(tabPane, Priority.ALWAYS);
+
+ Tab generalSettingsTab = new Tab("General");
+ generalSettings = new GeneralSettings(exceptionAndAlertHandler, serverListener, hostServices);
+ generalSettingsTab.setContent(generalSettings);
+
+ Tab pluginsSettingsTab = new Tab("Plugins");
+ pluginsSettings = new PluginsSettings(exceptionAndAlertHandler, hostServices);
+ pluginsSettingsTab.setContent(pluginsSettings);
+
+ Tab themesSettingsTab = new Tab("Themes");
+ themesSettings = new ThemesSettings();
+ themesSettingsTab.setContent(themesSettings);
+
+ Tab clientsSettingsTab = new Tab("Clients");
+ clientsSettings = new ClientsSettings(exceptionAndAlertHandler, serverListener);
+ clientsSettingsTab.setContent(clientsSettings);
+
+
+ Tab aboutTab = new Tab("About");
+ aboutTab.setContent(new About(hostServices));
+
+ tabPane.getTabs().addAll(generalSettingsTab, pluginsSettingsTab, themesSettingsTab, clientsSettingsTab, aboutTab);
+
+ setAlignment(Pos.TOP_RIGHT);
+
+ closeButton = new Button("Close");
+ VBox.setMargin(closeButton, new Insets(10.0));
+
+ getChildren().addAll(tabPane, closeButton);
+
+ setCache(true);
+ setCacheHint(CacheHint.SPEED);
+ }
+
+ public void setDefaultTabToGeneral()
+ {
+ tabPane.getSelectionModel().selectFirst();
+ }
+
+ public Button getCloseButton()
+ {
+ return closeButton;
+ }
+
+ public GeneralSettings getGeneralSettings()
+ {
+ return generalSettings;
+ }
+
+ public PluginsSettings getPluginsSettings()
+ {
+ return pluginsSettings;
+ }
+
+ public ThemesSettings getThemesSettings()
+ {
+ return themesSettings;
+ }
+
+ public ClientsSettings getClientsSettings()
+ {
+ return clientsSettings;
+ }
+
+}
--- /dev/null
+++ b/src/main/java/com/StreamPi/Server/Window/Settings/ThemesSettings.java
@@ -0,0 +1,181 @@
+package com.StreamPi.Server.Window.Settings;
+
+import com.StreamPi.ActionAPI.Action.Action;
+import com.StreamPi.ActionAPI.ActionProperty.Property.ControlType;
+import com.StreamPi.ActionAPI.ActionProperty.Property.Property;
+import com.StreamPi.ActionAPI.NormalAction.NormalAction;
+import com.StreamPi.Server.Action.NormalActionPlugins;
+import com.StreamPi.Server.Connection.ServerListener;
+import com.StreamPi.Server.Controller.Controller;
+import com.StreamPi.Server.IO.Config;
+import com.StreamPi.ThemeAPI.Theme;
+import com.StreamPi.ThemeAPI.Themes;
+import com.StreamPi.Util.Exception.MinorException;
+import com.StreamPi.Util.Exception.SevereException;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.control.*;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class ThemesSettings extends VBox
+{
+ private VBox themesSettingsVBox;
+
+ private Controller controller;
+
+ private Logger logger;
+
+ public ThemesSettings()
+ {
+
+ getStyleClass().add("themes_settings");
+ logger = Logger.getLogger(ThemesSettings.class.getName());
+
+ setPadding(new Insets(10));
+
+ themesSettingsVBox = new VBox();
+ themesSettingsVBox.setSpacing(10.0);
+ themesSettingsVBox.setAlignment(Pos.TOP_CENTER);
+
+ ScrollPane scrollPane = new ScrollPane();
+ scrollPane.getStyleClass().add("themes_settings_scroll_pane");
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+ scrollPane.maxWidthProperty().bind(widthProperty().multiply(0.8));
+
+ themesSettingsVBox.prefWidthProperty().bind(scrollPane.widthProperty().subtract(10));
+ scrollPane.setContent(themesSettingsVBox);
+
+ VBox.setVgrow(scrollPane, Priority.ALWAYS);
+
+ setAlignment(Pos.TOP_CENTER);
+
+ getChildren().addAll(scrollPane);
+
+ toggleButtons = new ArrayList<>();
+
+ }
+
+ public void setController(Controller controller)
+ {
+ this.controller = controller;
+ }
+
+ private Themes themes;
+ private String currentThemeFullName;
+
+ public void setThemes(Themes themes)
+ {
+ this.themes = themes;
+ }
+
+ public void setCurrentThemeFullName(String currentThemeFullName)
+ {
+ this.currentThemeFullName = currentThemeFullName;
+ }
+
+ private ArrayList<ToggleButton> toggleButtons;
+
+ public void loadThemes()
+ {
+ toggleButtons.clear();
+
+ Platform.runLater(()-> themesSettingsVBox.getChildren().clear());
+
+
+ for(int i = 0; i<themes.getThemeList().size(); i++)
+ {
+ Theme theme = themes.getThemeList().get(i);
+
+ VBox vBox = new VBox();
+ vBox.setSpacing(5.0);
+
+ Label shortNameLabel = new Label(theme.getShortName());
+ shortNameLabel.getStyleClass().add("settings_themes_each_theme_heading");
+
+ Label authorLabel = new Label(theme.getAuthor());
+
+ Label fullNameLabel = new Label(theme.getFullName());
+
+ Label versionLabel = new Label("Version : "+theme.getVersion().getText());
+
+ ToggleButton toggleButton = new ToggleButton();
+
+ toggleButton.setSelected(theme.getFullName().equals(currentThemeFullName));
+ toggleButton.setId(theme.getFullName());
+
+
+ if(theme.getFullName().equals(currentThemeFullName))
+ {
+ toggleButton.setText("ON");
+ toggleButton.setSelected(true);
+ toggleButton.setDisable(true);
+ }
+ else
+ {
+ toggleButton.setText("OFF");
+ }
+
+ toggleButton.setOnAction(event -> {
+ ToggleButton toggleButton1 = (ToggleButton) event.getSource();
+
+
+ toggleButton.setText("ON");
+
+ try {
+ Config.getInstance().setCurrentThemeFullName(toggleButton1.getId());
+ Config.getInstance().save();
+
+
+ for(ToggleButton toggleButton2 : toggleButtons)
+ {
+ if(toggleButton2.getId().equals(Config.getInstance().getCurrentThemeFullName()))
+ {
+ toggleButton2.setDisable(true);
+ toggleButton2.setText("ON");
+ toggleButton2.setSelected(true);
+ }
+ else
+ {
+ toggleButton2.setDisable(false);
+ toggleButton2.setText("OFF");
+ toggleButton2.setSelected(false);
+ }
+ }
+
+ controller.initThemes();
+ }
+ catch (SevereException e)
+ {
+ controller.handleSevereException(e);
+ }
+ });
+
+ HBox hBox = new HBox(toggleButton);
+
+ Region region1 = new Region();
+ region1.setPrefHeight(5);
+
+ hBox.setAlignment(Pos.TOP_RIGHT);
+
+ vBox.getChildren().addAll(shortNameLabel, authorLabel, versionLabel, fullNameLabel, hBox, region1);
+
+
+ vBox.getStyleClass().add("settings_themes_each_theme");
+
+ Platform.runLater(()->themesSettingsVBox.getChildren().add(vBox));
+
+
+ toggleButtons.add(toggleButton);
+ }
+
+ }
+}
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,25 @@
+module com.StreamPi.Server {
+ uses com.StreamPi.ActionAPI.Action.Action;
+ uses com.StreamPi.ActionAPI.NormalAction.NormalAction;
+
+ requires com.StreamPi.ActionAPI;
+ requires com.StreamPi.Util;
+ requires com.StreamPi.ThemeAPI;
+
+ requires org.kordamp.ikonli.javafx;
+
+ requires java.xml;
+
+ requires javafx.base;
+ requires javafx.graphics;
+ requires javafx.controls;
+ requires javafx.media;
+
+ requires java.desktop;
+
+ requires java.sql;
+
+ requires org.json;
+
+ exports com.StreamPi.Server;
+}
\ No newline at end of file
Binary files /dev/null and b/src/main/resources/com/StreamPi/Server/Default.obj differ
Binary files /dev/null and b/src/main/resources/com/StreamPi/Server/Roboto.ttf differ
Binary files /dev/null and b/src/main/resources/com/StreamPi/Server/app_icon.png differ
--- /dev/null
+++ b/src/main/resources/com/StreamPi/Server/style.css
@@ -0,0 +1,189 @@
+.root {
+ -fx-font-family : 'Roboto';
+}
+
+.settings_about_streampi_header
+{
+ -fx-font-size : 25;
+}
+
+.settings_about_donate_hyperlink
+{
+ -fx-font-size : 19;
+ -fx-font-weight : bold;
+}
+
+.settings_about_license_label
+{
+ -fx-font-size : 16;
+}
+
+.settings_plugins_each_action, .settings_themes_each_theme, .settings_clients_each_client
+{
+ -fx-border-width : 0 0 1 0;
+ -fx-border-color : grey;
+}
+
+.settings_plugins_each_action_heading, .settings_themes_each_theme_heading, .settings_client_nick_name_label
+{
+ -fx-font-size : 19;
+}
+
+.action_box
+{
+ -fx-border-width: 1px;
+ -fx-shape: "M100,100 h200 a20,20 0 0 1 20,20 v200 a20,20 0 0 1 -20,20 h-200 a20,20 0 0 1 -20,-20 v-200 a20,20 0 0 1 20,-20 z";
+}
+
+.action_box_icon_present
+{
+
+}
+
+.action_box_icon_not_present
+{
+
+}
+
+.action_box_valid
+{
+ -fx-border-color: grey;
+}
+
+.action_box_invalid
+{
+ -fx-border-color: red;
+}
+
+.action_box_display_text_label
+{
+ -fx-font-size : 16;
+ -fx-background-color : transparent;
+}
+
+.action_details_pane_action_heading_label
+{
+ -fx-font-size : 16;
+}
+
+.settings_client_socket_connection_label
+{
+ -fx-font-size : 16;
+}
+
+
+.alert_header
+{
+ -fx-padding: 5;
+}
+
+.alert_pane
+{
+ -fx-background-color : white;
+ -fx-border-color : white;
+ -fx-border-width : 5;
+ -fx-border-radius : 5;
+ -fx-background-radius : 5;
+ -fx-max-height : 300;
+ -fx-max-width : 410;
+ -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0.0 , 0 );
+}
+
+.alert_header_icon
+{
+ -fx-icon-size : 30;
+}
+
+.alert_error_icon
+{
+ -fx-icon-color: RED;
+}
+
+.alert_information_icon
+{
+ -fx-icon-color: BLUE;
+}
+
+.alert_warning_icon
+{
+ -fx-icon-color: #ffcc00;
+}
+
+.alert_content_pane
+{
+ -fx-background-color: white;
+ -fx-padding: 5;
+}
+
+.alert_pane_parent, .combobox_pane_parent
+{
+ -fx-background-color : rgba(0,0,0,0.5);
+}
+
+.alert_pane_header_text
+{
+ -fx-font-size: 18;
+}
+
+.alert_button_bar
+{
+ -fx-background-color: white;
+ -fx-alignment: CENTER_RIGHT;
+ -fx-spacing: 5;
+ -fx-padding: 5;
+}
+
+.alert_scroll_pane {
+ -fx-background: #FFFFFF;
+ -fx-border-color:#FFFFFF;
+ /*-fx-focus-color: #FFFFFF;
+ -fx-faint-focus-color:#FFFFFF;*/
+}
+
+.dashboard_plugins_pane_action_icon_imageview
+{
+ -fx-width:20;
+ -fx-height:20;
+}
+
+.donate_request_popup_patreon_link
+{
+ -fx-padding : 10 0 0 0;
+ -fx-font-size : 15;
+}
+
+
+.first_time_use_pane
+{
+ -fx-padding : 5;
+}
+
+.first_time_use_pane_heading_label
+{
+ -fx-font-size: 20;
+}
+
+.first_time_use_pane_stackpane
+{
+
+}
+
+.first_time_use_pane_welcome
+{
+
+}
+
+.first_time_use_pane_license
+{
+ -fx-spacing : 10;
+}
+
+.first_time_use_pane_final_config
+{
+
+}
+
+.first_time_use_pane_final_config_label
+{
+ -fx-padding: 0 0 30 0;
+}
\ No newline at end of file