mirror of
https://github.com/octopusYan/dayz-mod-translator.git
synced 2024-11-21 19:56:42 +08:00
Compare commits
3 Commits
50032cc599
...
9edc015af0
Author | SHA1 | Date | |
---|---|---|---|
9edc015af0 | |||
1dc7a64833 | |||
943056168f |
5
.gitignore
vendored
5
.gitignore
vendored
@ -7,6 +7,11 @@ target/
|
|||||||
!**/src/main/**/target/
|
!**/src/main/**/target/
|
||||||
!**/src/test/**/target/
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
/bin
|
||||||
|
/tmp
|
||||||
|
/bak
|
||||||
|
config.yaml
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea/
|
.idea/
|
||||||
.idea/modules.xml
|
.idea/modules.xml
|
||||||
|
661
LICENSE
Normal file
661
LICENSE
Normal file
@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
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 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 work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero 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 your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
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 AGPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
108
README.md
108
README.md
@ -2,46 +2,35 @@
|
|||||||
|
|
||||||
# DayZ Mod Translator
|
# DayZ Mod Translator
|
||||||
|
|
||||||
|
![JDK](https://img.shields.io/badge/JDK-21-%2300599C)
|
||||||
|
[![JavaFX](https://img.shields.io/badge/JavaFX-21.0.4-%2300599C)](https://openjfx.io/)
|
||||||
|
![platform](https://img.shields.io/badge/platform-Windows-blueviolet)
|
||||||
<br>
|
<br>
|
||||||
<div>
|
[![license](https://img.shields.io/github/license/octopusYan/dayz-mod-translator)](https://github.com/octopusYan/dayz-mod-translator)
|
||||||
<img alt="JDK" src="https://img.shields.io/badge/JDK-17-%2300599C">
|
![commit](https://img.shields.io/github/commit-activity/m/octopusYan/dayz-mod-translator?color=%23ff69b4)
|
||||||
<img alt="platform" src="https://img.shields.io/badge/platform-Windows-blueviolet">
|
<br>
|
||||||
</div>
|
![stars](https://img.shields.io/github/stars/octopusYan/dayz-mod-translator?style=social)
|
||||||
|
![GitHub all releases](https://img.shields.io/github/downloads/octopusYan/dayz-mod-translator/total?style=social)
|
||||||
[//]: # (<div>)
|
|
||||||
|
|
||||||
[//]: # ( <img alt="license" src="https://img.shields.io/github/license/octopusYan/dayz-mod-translator">)
|
|
||||||
|
|
||||||
[//]: # ( <img alt="commit" src="https://img.shields.io/github/commit-activity/m/octopusYan/dayz-mod-translator?color=%23ff69b4">)
|
|
||||||
|
|
||||||
[//]: # (</div>)
|
|
||||||
|
|
||||||
[//]: # (<div>)
|
|
||||||
|
|
||||||
[//]: # ( <img alt="stars" src="https://img.shields.io/github/stars/octopusYan/dayz-mod-translator?style=social">)
|
|
||||||
|
|
||||||
[//]: # ( <img alt="GitHub all releases" src="https://img.shields.io/github/downloads/octopusYan/dayz-mod-translator/total?style=social">)
|
|
||||||
|
|
||||||
[//]: # (</div>)
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
使用 JavaFx 编写的 DayZ 游戏mod 汉化GUI工具
|
使用JavaFx编写的DayZ/ArmA游戏模组汉化工具
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### 使用
|
### 使用
|
||||||
|
|
||||||
- 设置 -> 翻译,选择翻译接口,填写配置信息,点击确定
|
- 设置 -> 翻译,选择翻译接口,填写配置信息,点击确定
|
||||||
- 点击左侧打开文件选择需要翻译的模组pbo文件
|
- 点击打开文件按钮选择需要翻译的模组pbo文件
|
||||||
- 待翻译文本获取完成后,点击右侧翻译按钮
|
- 等待待可翻译文本获取完成,点击右侧翻译按钮
|
||||||
- 翻译完成后,点击打包按钮,选择保存位置
|
- 翻译完成后,点击打包按钮,选择保存位置
|
||||||
|
|
||||||
<details><summary>截图</summary>
|
<details open>
|
||||||
|
<summary>截图</summary>
|
||||||
|
|
||||||
![Main window start](doc/img/screenshot01.png 'Main application window start')
|
![start](doc/img/screenshot01.png 'start')
|
||||||
|
![open file](doc/img/screenshot02.png 'open file')
|
||||||
![Main window open file](doc/img/screenshot02.png 'Main window open file')
|
![edit](doc/img/screenshot03.png 'edit')
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@ -49,43 +38,50 @@
|
|||||||
|
|
||||||
#### 环境说明
|
#### 环境说明
|
||||||
|
|
||||||
| 名称 | 描述 |
|
| 名称 | 描述 |
|
||||||
|-------|---------------------------------------------------------|
|
|------|---------------------------------------------------------|
|
||||||
| 系统环境 | windows11/10 |
|
| 系统环境 | windows 10/11 |
|
||||||
| JDK版本 | 17 |
|
| JDK | 21 |
|
||||||
| 构建工具 | Mavne |
|
| 构建工具 | Maven |
|
||||||
| 打包工具 | [JavaPackager](https://github.com/fvarrui/JavaPackager) |
|
| 打包工具 | [JavaPackager](https://github.com/fvarrui/JavaPackager) |
|
||||||
|
|
||||||
#### 步骤
|
#### 本地运行
|
||||||
|
|
||||||
1. 环境配置
|
1. 克隆代码
|
||||||
- 打开 [JavaFx](https://gluonhq.com/products/javafx/) 官网环境下载页面
|
|
||||||
- 在下方 Downloads 处
|
|
||||||
- `JavaFX version` 选择 `17.0.12[LTS]`
|
|
||||||
- `Operating System` 选择 `Windows`
|
|
||||||
- `Type` 选择 `jmods`
|
|
||||||
- 点击右侧绿色按钮下载解压
|
|
||||||
- 将解压文件夹内所有 `.jmod` 后缀名的文件复制到 `jdk环境目录` 的`jmods`文件夹中
|
|
||||||
2. 下载源代码并使用 [IntelliJ IDEA](https://www.jetbrains.com/zh-cn/idea/download/?section=windows) 打开
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/octopusYan/dayz-mod-translator.git
|
git clone https://github.com/octopusYan/dayz-mod-translator
|
||||||
|
```
|
||||||
|
2. 运行
|
||||||
|
```bash
|
||||||
|
mvn clean javafx:run
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 打包
|
||||||
|
|
||||||
|
1. 克隆代码
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/octopusYan/dayz-mod-translator
|
||||||
|
```
|
||||||
|
2. 运行
|
||||||
|
```bash
|
||||||
|
mvn clean package
|
||||||
```
|
```
|
||||||
3. 打包
|
|
||||||
- 使用 `IntelliJ IDEA` Maven UI
|
|
||||||
- 点击右侧工具栏的`Maven`
|
|
||||||
- 展开 `DayzModTranslator\Lifecycle`
|
|
||||||
- 点击 package
|
|
||||||
- 使用 mvn 命令
|
|
||||||
```bash
|
|
||||||
mvn package
|
|
||||||
```
|
|
||||||
|
|
||||||
### 可能会用到
|
### 可能会用到
|
||||||
|
|
||||||
吾爱论坛 / 谷歌翻译修复:[谷歌浏览器右键翻译失效了咋办,一键修复,才13KB](https://www.52pojie.cn/thread-1781877-1-1.html)
|
吾爱论坛 / 谷歌翻译修复:[谷歌浏览器右键翻译失效了咋办,一键修复,才13KB](https://www.52pojie.cn/thread-1781877-1-1.html)
|
||||||
|
|
||||||
## 致谢
|
### 依赖/引用的项目
|
||||||
|
|
||||||
### 开源库
|
<figure>
|
||||||
|
|
||||||
- [pboman3](https://github.com/winseros/pboman3):打开、打包和解包 ArmA PBO 文件的工具。
|
| | |
|
||||||
|
|-----------------------------------------------------------------------------|--------------------------|
|
||||||
|
| [PBO Manager](https://github.com/winseros/pboman3) | 打开、打包和解包 ArmA PBO 文件的工具。 |
|
||||||
|
| [JavaFX](https://openjfx.io/) | Java 桌面开发 |
|
||||||
|
| [AtlantaFX](https://mkpaz.github.io/atlantafx/) | JavaFX CSS 主题集合 |
|
||||||
|
| [JavaPackager](https://github.com/fvarrui/JavaPackager) | 打包插件 |
|
||||||
|
| [Apache Commons](https://commons.apache.org/proper/commons-exec/index.html) | 工具包 |
|
||||||
|
| [SLF4J](https://slf4j.org/) | 日志工具 |
|
||||||
|
|
||||||
|
</figure>
|
Binary file not shown.
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 44 KiB |
Binary file not shown.
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 146 KiB |
BIN
doc/img/screenshot03.png
Normal file
BIN
doc/img/screenshot03.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 135 KiB |
162
pom.xml
162
pom.xml
@ -5,28 +5,40 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>cn.octopusyan</groupId>
|
<groupId>cn.octopusyan</groupId>
|
||||||
<artifactId>dayz-mod-translator</artifactId>
|
<artifactId>dmt</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>0.0.2</version>
|
||||||
<name>DayzModTranslator</name>
|
<name>dmt</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
<name>octopus_yan</name>
|
<name>octopus_yan</name>
|
||||||
<url>octopus_yan@foxmail.com</url>
|
<url>octopus_yan@foxmail.com</url>
|
||||||
</organization>
|
</organization>
|
||||||
|
|
||||||
<inceptionYear>2024</inceptionYear>
|
<inceptionYear>2024</inceptionYear>
|
||||||
<description>DayZ 模组汉化工具</description>
|
<description>DayZ/ArmA 模组汉化工具</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<java.version>17</java.version>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
|
||||||
|
<exec.mainClass>cn.octopusyan.dmt.AppLauncher</exec.mainClass>
|
||||||
|
<cssSrcPath>${project.basedir}/src/main/resources/css</cssSrcPath>
|
||||||
|
<cssTargetPath>${project.basedir}/target/classes/css</cssTargetPath>
|
||||||
|
|
||||||
<junit.version>5.11.0</junit.version>
|
<junit.version>5.11.0</junit.version>
|
||||||
<javafx.version>17.0.12</javafx.version>
|
<javafx.version>21.0.4</javafx.version>
|
||||||
<slf4j.version>2.0.16</slf4j.version>
|
<slf4j.version>2.0.16</slf4j.version>
|
||||||
<logback.version>1.5.7</logback.version>
|
<logback.version>1.5.7</logback.version>
|
||||||
<fastjson.version>2.0.52</fastjson.version>
|
<fastjson.version>2.0.52</fastjson.version>
|
||||||
|
<common-lang3.version>3.16.0</common-lang3.version>
|
||||||
|
<common-io.version>2.17.0</common-io.version>
|
||||||
|
<common-exec.version>1.4.0</common-exec.version>
|
||||||
|
<lombok.version>1.18.32</lombok.version>
|
||||||
|
<jackson.version>2.15.4</jackson.version>
|
||||||
|
<ikonli.version>12.3.1</ikonli.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -47,12 +59,13 @@
|
|||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- slf4j -->
|
<!-- https://mkpaz.github.io/atlantafx/ -->
|
||||||
<!--<dependency>-->
|
<dependency>
|
||||||
<!-- <groupId>org.slf4j</groupId>-->
|
<groupId>io.github.mkpaz</groupId>
|
||||||
<!-- <artifactId>slf4j-simple</artifactId>-->
|
<artifactId>atlantafx-base</artifactId>
|
||||||
<!-- <version>${slf4j.version}</version>-->
|
<version>2.0.1</version>
|
||||||
<!--</dependency>-->
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
@ -88,30 +101,62 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>3.16.0</version>
|
<version>${common-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
|
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>2.16.1</version>
|
<version>${common-io.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-exec</artifactId>
|
<artifactId>commons-exec</artifactId>
|
||||||
<version>1.4.0</version>
|
<version>${common-exec.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JSON -->
|
<!-- lombok -->
|
||||||
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.fastjson2</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>fastjson2</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>${fastjson.version}</version>
|
<version>${lombok.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- jackson -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://kordamp.org/ikonli/ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
|
<artifactId>ikonli-javafx</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
|
<artifactId>ikonli-fontawesome-pack</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
|
<artifactId>ikonli-feather-pack</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
|
<artifactId>ikonli-bom</artifactId>
|
||||||
|
<version>${ikonli.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
<pluginRepositories>
|
<pluginRepositories>
|
||||||
<pluginRepository>
|
<pluginRepository>
|
||||||
<id>nexus</id>
|
<id>nexus</id>
|
||||||
@ -141,8 +186,17 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.13.0</version>
|
<version>3.13.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>17</source>
|
<source>21</source>
|
||||||
<target>17</target>
|
<target>21</target>
|
||||||
|
<compilerArgs>--enable-preview</compilerArgs>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
@ -162,20 +216,25 @@
|
|||||||
<groupId>org.openjfx</groupId>
|
<groupId>org.openjfx</groupId>
|
||||||
<artifactId>javafx-maven-plugin</artifactId>
|
<artifactId>javafx-maven-plugin</artifactId>
|
||||||
<version>0.0.8</version>
|
<version>0.0.8</version>
|
||||||
|
<configuration>
|
||||||
|
<stripDebug>true</stripDebug>
|
||||||
|
<compress>2</compress>
|
||||||
|
<noHeaderFiles>true</noHeaderFiles>
|
||||||
|
<noManPages>true</noManPages>
|
||||||
|
<launcher>launcher</launcher>
|
||||||
|
<jlinkImageName>app</jlinkImageName>
|
||||||
|
<jlinkZipName>app</jlinkZipName>
|
||||||
|
<mainClass>cn.octopusyan.dmt/${exec.mainClass}</mainClass>
|
||||||
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
<!-- Default configuration for running with: mvn clean javafx:run -->
|
||||||
<id>default-cli</id>
|
<id>default-cli</id>
|
||||||
<configuration>
|
<configuration>
|
||||||
<mainClass>
|
<options>
|
||||||
cn.octopusyan.dayzmodtranslator/cn.octopusyan.dayzmodtranslator.AppLuncher
|
<option>--enable-preview</option>
|
||||||
</mainClass>
|
<!-- <option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005</option>-->
|
||||||
<launcher>launcher</launcher>
|
</options>
|
||||||
<jlinkZipName>app</jlinkZipName>
|
|
||||||
<jlinkImageName>app</jlinkImageName>
|
|
||||||
<noManPages>true</noManPages>
|
|
||||||
<stripDebug>true</stripDebug>
|
|
||||||
<noHeaderFiles>true</noHeaderFiles>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
@ -186,20 +245,53 @@
|
|||||||
<artifactId>javapackager</artifactId>
|
<artifactId>javapackager</artifactId>
|
||||||
<version>1.7.7-SNAPSHOT</version>
|
<version>1.7.7-SNAPSHOT</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<mainClass>${exec.mainClass}</mainClass>
|
||||||
<bundleJre>true</bundleJre>
|
<bundleJre>true</bundleJre>
|
||||||
<mainClass>cn.octopusyan.dayzmodtranslator.AppLuncher</mainClass>
|
|
||||||
<generateInstaller>false</generateInstaller>
|
<generateInstaller>false</generateInstaller>
|
||||||
|
<copyDependencies>true</copyDependencies>
|
||||||
|
<vmArgs>
|
||||||
|
<arg>--enable-preview</arg>
|
||||||
|
<arg>-Xmx100m</arg>
|
||||||
|
</vmArgs>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>bundling-for-windows</id>
|
<id>windows</id>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>package</goal>
|
<goal>package</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<platform>windows</platform>
|
<platform>windows</platform>
|
||||||
|
<zipballName>${project.name}-windows</zipballName>
|
||||||
<createZipball>true</createZipball>
|
<createZipball>true</createZipball>
|
||||||
|
<winConfig>
|
||||||
|
<headerType>gui</headerType>
|
||||||
|
<generateMsi>false</generateMsi>
|
||||||
|
</winConfig>
|
||||||
|
<additionalResources>
|
||||||
|
<additionalResource>${project.basedir}/src/main/resources/bin</additionalResource>
|
||||||
|
</additionalResources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>windows-nojre</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>package</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<zipballName>${project.name}-windows-nojre</zipballName>
|
||||||
|
<platform>windows</platform>
|
||||||
|
<createZipball>true</createZipball>
|
||||||
|
<bundleJre>false</bundleJre>
|
||||||
|
<winConfig>
|
||||||
|
<headerType>gui</headerType>
|
||||||
|
<generateMsi>false</generateMsi>
|
||||||
|
</winConfig>
|
||||||
|
<additionalResources>
|
||||||
|
<additionalResource>${project.basedir}/src/main/resources/bin</additionalResource>
|
||||||
|
</additionalResources>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动类
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class AppLuncher {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
// try {
|
|
||||||
// Runtime.getRuntime().exec("Taskkill /IM " + FrpManager.FRPC_CLIENT_FILE_NAME + " /f");
|
|
||||||
// } catch (IOException e) {
|
|
||||||
// e.printStackTrace();
|
|
||||||
// }
|
|
||||||
|
|
||||||
Application.launch(Application.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,105 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpConfig;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.controller.MainController;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.CfgConvertUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.PBOUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadPoolManager;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.FxmlUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpUtil;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.ProxySelector;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class Application extends javafx.application.Application {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(Application.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init() throws Exception {
|
|
||||||
logger.info("application init ...");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(Stage stage) throws IOException {
|
|
||||||
|
|
||||||
logger.info("application start ...");
|
|
||||||
|
|
||||||
// bin转换 工具初始化
|
|
||||||
CfgConvertUtil.init();
|
|
||||||
|
|
||||||
// PBO 工具初始化
|
|
||||||
PBOUtil.init(CfgConvertUtil.getInstance());
|
|
||||||
|
|
||||||
// 客户端配置初始化
|
|
||||||
CustomConfig.init();
|
|
||||||
|
|
||||||
// 初始化弹窗工具
|
|
||||||
AlertUtil.initOwner(stage);
|
|
||||||
|
|
||||||
// http请求工具初始化
|
|
||||||
HttpConfig httpConfig = new HttpConfig();
|
|
||||||
if (CustomConfig.hasProxy()) {
|
|
||||||
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(CustomConfig.proxyHost(), CustomConfig.proxyPort());
|
|
||||||
httpConfig.setProxySelector(ProxySelector.of(unresolved));
|
|
||||||
}
|
|
||||||
httpConfig.setConnectTimeout(10);
|
|
||||||
HttpUtil.init(httpConfig);
|
|
||||||
|
|
||||||
// TODO 全局异常处理
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
|
|
||||||
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
|
|
||||||
|
|
||||||
// 启动主界面
|
|
||||||
try {
|
|
||||||
FXMLLoader fxmlLoader = FxmlUtil.load("main-view");
|
|
||||||
VBox root = fxmlLoader.load();//底层面板
|
|
||||||
Scene scene = new Scene(root);
|
|
||||||
scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
|
|
||||||
stage.setScene(scene);
|
|
||||||
stage.setMinHeight(330);
|
|
||||||
stage.setMinWidth(430);
|
|
||||||
stage.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
stage.setMaxHeight(Double.MAX_VALUE);
|
|
||||||
stage.setTitle(AppConstant.APP_TITLE + " v" + AppConstant.APP_VERSION);
|
|
||||||
stage.show();
|
|
||||||
|
|
||||||
MainController controller = fxmlLoader.getController();
|
|
||||||
controller.setApplication(this);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
showErrorDialog(Thread.currentThread(), t);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("application start over ...");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showErrorDialog(Thread t, Throwable e) {
|
|
||||||
logger.error("", e);
|
|
||||||
AlertUtil.exceptionAlert(new Exception(e)).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void stop() throws Exception {
|
|
||||||
logger.info("application stop ...");
|
|
||||||
|
|
||||||
// 清除翻译任务
|
|
||||||
TranslateUtil.getInstance().clear();
|
|
||||||
// 停止所有线程
|
|
||||||
ThreadPoolManager.getInstance().shutdown();
|
|
||||||
// 保存应用数据
|
|
||||||
CustomConfig.store();
|
|
||||||
// 清理缓存
|
|
||||||
PBOUtil.clear();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,217 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.base;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.FxmlUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.Loading;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.TooltipUtil;
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.fxml.Initializable;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.stage.Modality;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.ResourceBundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用视图控制器基类
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public abstract class BaseController<P extends Pane> implements Initializable {
|
|
||||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
private Application application;
|
|
||||||
|
|
||||||
private double xOffSet = 0, yOffSet = 0;
|
|
||||||
|
|
||||||
private volatile Loading loading;
|
|
||||||
|
|
||||||
protected TooltipUtil tooltipUtil;
|
|
||||||
|
|
||||||
public void jumpTo(BaseController<P> controller) throws IOException {
|
|
||||||
FXMLLoader fxmlLoader = FxmlUtil.load(controller.getRootFxml());
|
|
||||||
|
|
||||||
Scene scene = getRootPanel().getScene();
|
|
||||||
double oldHeight = getRootPanel().getPrefHeight();
|
|
||||||
double oldWidth = getRootPanel().getPrefWidth();
|
|
||||||
|
|
||||||
Pane root = fxmlLoader.load();
|
|
||||||
Stage stage = (Stage) scene.getWindow();
|
|
||||||
// 窗口大小
|
|
||||||
double newWidth = root.getPrefWidth();
|
|
||||||
double newHeight = root.getPrefHeight();
|
|
||||||
// 窗口位置
|
|
||||||
double newX = stage.getX() - (newWidth - oldWidth) / 2;
|
|
||||||
double newY = stage.getY() - (newHeight - oldHeight) / 2;
|
|
||||||
scene.setRoot(root);
|
|
||||||
stage.setX(newX < 0 ? 0 : newX);
|
|
||||||
stage.setY(newY < 0 ? 0 : newY);
|
|
||||||
stage.setWidth(newWidth);
|
|
||||||
stage.setHeight(newHeight);
|
|
||||||
|
|
||||||
controller = fxmlLoader.getController();
|
|
||||||
controller.setApplication(getApplication());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void open(Class<? extends BaseController<?>> clazz, String title) {
|
|
||||||
try {
|
|
||||||
FXMLLoader load = FxmlUtil.load(clazz.getDeclaredConstructor().newInstance().getRootFxml());
|
|
||||||
Parent root = load.load();
|
|
||||||
Scene scene = new Scene(root);
|
|
||||||
scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
|
|
||||||
Stage stage = new Stage();
|
|
||||||
stage.setScene(scene);
|
|
||||||
stage.setTitle(title);
|
|
||||||
stage.initOwner(getWindow());
|
|
||||||
stage.initModality(Modality.WINDOW_MODAL);
|
|
||||||
stage.show();
|
|
||||||
load.getController();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
|
||||||
// 全局窗口拖拽
|
|
||||||
if (dragWindow()) {
|
|
||||||
// 窗口拖拽
|
|
||||||
getRootPanel().setOnMousePressed(event -> {
|
|
||||||
xOffSet = event.getSceneX();
|
|
||||||
yOffSet = event.getSceneY();
|
|
||||||
});
|
|
||||||
getRootPanel().setOnMouseDragged(event -> {
|
|
||||||
Stage stage = (Stage) getWindow();
|
|
||||||
stage.setX(event.getScreenX() - xOffSet);
|
|
||||||
stage.setY(event.getScreenY() - yOffSet);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 窗口初始化完成监听
|
|
||||||
getRootPanel().sceneProperty().addListener((observable, oldValue, newValue) -> {
|
|
||||||
newValue.windowProperty().addListener(new ChangeListener<Window>() {
|
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
|
|
||||||
//关闭窗口监听
|
|
||||||
getWindow().setOnCloseRequest(windowEvent -> onDestroy());
|
|
||||||
|
|
||||||
// app 版本信息
|
|
||||||
if (getAppVersionLabel() != null) getAppVersionLabel().setText("v" + AppConstant.APP_VERSION);
|
|
||||||
|
|
||||||
// 初始化数据
|
|
||||||
initData();
|
|
||||||
|
|
||||||
// 初始化视图样式
|
|
||||||
initViewStyle();
|
|
||||||
|
|
||||||
// 初始化视图事件
|
|
||||||
initViewAction();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showLoading() {
|
|
||||||
showLoading(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showLoading(String message) {
|
|
||||||
if (loading == null) loading = new Loading((Stage) getWindow());
|
|
||||||
|
|
||||||
if (StringUtils.isNotEmpty(message)) loading.showMessage(message);
|
|
||||||
|
|
||||||
loading.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApplication(Application application) {
|
|
||||||
this.application = application;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Application getApplication() {
|
|
||||||
return application;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLoadShowing() {
|
|
||||||
return loading != null && loading.showing();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stopLoading() {
|
|
||||||
if (isLoadShowing())
|
|
||||||
loading.closeStage();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TooltipUtil getTooltipUtil() {
|
|
||||||
if (tooltipUtil == null) tooltipUtil = TooltipUtil.getInstance(getRootPanel());
|
|
||||||
return tooltipUtil;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 窗口拖拽设置
|
|
||||||
*
|
|
||||||
* @return 是否启用
|
|
||||||
*/
|
|
||||||
public abstract boolean dragWindow();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取根布局
|
|
||||||
*
|
|
||||||
* @return 根布局对象
|
|
||||||
*/
|
|
||||||
public abstract P getRootPanel();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取根布局
|
|
||||||
* <p> 搭配 <code>FxmlUtil.load</code> 使用
|
|
||||||
*
|
|
||||||
* @return 根布局对象
|
|
||||||
* @see cn.octopusyan.dayzmodtranslator.util.FxmlUtil#load(String)
|
|
||||||
*/
|
|
||||||
public abstract String getRootFxml();
|
|
||||||
|
|
||||||
protected Window getWindow() {
|
|
||||||
return getRootPanel().getScene().getWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* App版本信息标签
|
|
||||||
*/
|
|
||||||
public Label getAppVersionLabel() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化数据
|
|
||||||
*/
|
|
||||||
public abstract void initData();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图样式
|
|
||||||
*/
|
|
||||||
public abstract void initViewStyle();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图事件
|
|
||||||
*/
|
|
||||||
public abstract void initViewAction();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭窗口
|
|
||||||
*/
|
|
||||||
public void onDestroy() {
|
|
||||||
Stage stage = (Stage) getWindow();
|
|
||||||
stage.hide();
|
|
||||||
stage.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.config;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.PropertiesUtils;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用信息
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class AppConstant {
|
|
||||||
public static final String APP_TITLE = PropertiesUtils.getInstance().getProperty("app.title");
|
|
||||||
public static final String APP_NAME = PropertiesUtils.getInstance().getProperty("app.name");
|
|
||||||
public static final String APP_VERSION = PropertiesUtils.getInstance().getProperty("app.version");
|
|
||||||
public static final String DATA_DIR_PATH = System.getProperty("user.home") + File.separator + "AppData" + File.separator + "Local" + File.separator + APP_NAME;
|
|
||||||
public static final String TMP_DIR_PATH = FileUtils.getTempDirectoryPath() + APP_NAME;
|
|
||||||
public static final String CUSTOM_CONFIG_PATH = DATA_DIR_PATH + File.separator + "config.properties";
|
|
||||||
public static final String BAK_FILE_PATH = AppConstant.TMP_DIR_PATH + File.separator + "bak";
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.config;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端设置
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class CustomConfig {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CustomConfig.class);
|
|
||||||
private static final Properties properties = new Properties();
|
|
||||||
public static final String PROXY_HOST_KEY = "proxy.host";
|
|
||||||
public static final String PROXY_PORT_KEY = "proxy.port";
|
|
||||||
public static final String TRANSLATE_SOURCE_KEY = "translate.source";
|
|
||||||
public static final String TRANSLATE_SOURCE_APPID_KEY = "translate.{}.appid";
|
|
||||||
public static final String TRANSLATE_SOURCE_APIKEY_KEY = "translate.{}.apikey";
|
|
||||||
public static final String TRANSLATE_SOURCE_QPS_KEY = "translate.{}.qps";
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
File customConfigFile = new File(AppConstant.CUSTOM_CONFIG_PATH);
|
|
||||||
try {
|
|
||||||
if (!customConfigFile.exists()) {
|
|
||||||
// 初始配置
|
|
||||||
properties.put(TRANSLATE_SOURCE_KEY, TranslateSource.FREE_GOOGLE.getName());
|
|
||||||
// 保存配置文件
|
|
||||||
store();
|
|
||||||
} else {
|
|
||||||
properties.load(new FileInputStream(customConfigFile));
|
|
||||||
}
|
|
||||||
} catch (IOException ignore) {
|
|
||||||
logger.error("读取配置文件失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否配置代理
|
|
||||||
*/
|
|
||||||
public static boolean hasProxy() {
|
|
||||||
String host = proxyHost();
|
|
||||||
Integer port = proxyPort();
|
|
||||||
|
|
||||||
return StringUtils.isNoneBlank(host) && null != port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 代理地址
|
|
||||||
*/
|
|
||||||
public static String proxyHost() {
|
|
||||||
return properties.getProperty(PROXY_HOST_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 代理地址
|
|
||||||
*/
|
|
||||||
public static void proxyHost(String host) {
|
|
||||||
properties.setProperty(PROXY_HOST_KEY, host);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 代理端口
|
|
||||||
*/
|
|
||||||
public static Integer proxyPort() {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(properties.getProperty(PROXY_PORT_KEY));
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 代理端口
|
|
||||||
*/
|
|
||||||
public static void proxyPort(int port) {
|
|
||||||
properties.setProperty(PROXY_PORT_KEY, String.valueOf(port));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译源
|
|
||||||
*/
|
|
||||||
public static TranslateSource translateSource() {
|
|
||||||
String name = properties.getProperty(TRANSLATE_SOURCE_KEY, TranslateSource.FREE_GOOGLE.getName());
|
|
||||||
return TranslateSource.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译源
|
|
||||||
*/
|
|
||||||
public static void translateSource(TranslateSource source) {
|
|
||||||
properties.setProperty(TRANSLATE_SOURCE_KEY, source.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否配置接口认证
|
|
||||||
*
|
|
||||||
* @param source 翻译源
|
|
||||||
*/
|
|
||||||
public static boolean hasTranslateApiKey(TranslateSource source) {
|
|
||||||
return StringUtils.isNoneBlank(translateSourceAppid(source))
|
|
||||||
&& StringUtils.isNoneBlank(translateSourceApikey(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置翻译源appid
|
|
||||||
*
|
|
||||||
* @param source 翻译源
|
|
||||||
* @param appid appid
|
|
||||||
*/
|
|
||||||
public static void translateSourceAppid(TranslateSource source, String appid) {
|
|
||||||
properties.setProperty(getTranslateSourceAppidKey(source), appid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取翻译源appid
|
|
||||||
*
|
|
||||||
* @param source 翻译源
|
|
||||||
* @return appid
|
|
||||||
*/
|
|
||||||
public static String translateSourceAppid(TranslateSource source) {
|
|
||||||
return properties.getProperty(getTranslateSourceAppidKey(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void translateSourceApikey(TranslateSource source, String apikey) {
|
|
||||||
properties.setProperty(getTranslateSourceApikeyKey(source), apikey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String translateSourceApikey(TranslateSource source) {
|
|
||||||
return properties.getProperty(getTranslateSourceApikeyKey(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Integer translateSourceQps(TranslateSource source) {
|
|
||||||
String qpsStr = properties.getProperty(getTranslateSourceQpsKey(source));
|
|
||||||
return qpsStr == null ? source.getDefaultQps() : Integer.parseInt(qpsStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void translateSourceQps(TranslateSource source, int qps) {
|
|
||||||
properties.setProperty(getTranslateSourceQpsKey(source), String.valueOf(qps));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存配置
|
|
||||||
*/
|
|
||||||
public static void store() {
|
|
||||||
// 生成配置文件
|
|
||||||
try {
|
|
||||||
properties.store(new PrintStream(AppConstant.CUSTOM_CONFIG_PATH), String.valueOf(StandardCharsets.UTF_8));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("保存客户端配置失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getTranslateSourceAppidKey(TranslateSource source) {
|
|
||||||
return StringUtils.replace(TRANSLATE_SOURCE_APPID_KEY, "{}", source.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getTranslateSourceApikeyKey(TranslateSource source) {
|
|
||||||
return StringUtils.replace(TRANSLATE_SOURCE_APIKEY_KEY, "{}", source.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getTranslateSourceQpsKey(TranslateSource source) {
|
|
||||||
return StringUtils.replace(TRANSLATE_SOURCE_QPS_KEY, "{}", source.getName());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,515 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.controller;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.base.BaseController;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.PBOUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.file.FileTreeItem;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordCsvItem;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordItem;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.ClipUtil;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.event.EventHandler;
|
|
||||||
import javafx.scene.control.*;
|
|
||||||
import javafx.scene.control.cell.TextFieldTableCell;
|
|
||||||
import javafx.scene.input.*;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.stage.FileChooser;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主控制器
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class MainController extends BaseController<VBox> {
|
|
||||||
|
|
||||||
public VBox root;
|
|
||||||
public MenuItem openFileSetupBtn;
|
|
||||||
public MenuItem translateSetupBtn;
|
|
||||||
public MenuItem proxySetupBtn;
|
|
||||||
public Label filePath;
|
|
||||||
public StackPane fileBox;
|
|
||||||
public VBox openFileBox;
|
|
||||||
public Button openFile;
|
|
||||||
public VBox dragFileBox;
|
|
||||||
public Label dragFileLabel;
|
|
||||||
public VBox loadFileBox;
|
|
||||||
public Label loadFileLabel;
|
|
||||||
public ProgressBar loadFileProgressBar;
|
|
||||||
public TreeView<String> treeFileBox;
|
|
||||||
public StackPane wordBox;
|
|
||||||
public TableView<WordItem> wordTableBox;
|
|
||||||
public VBox wordMsgBox;
|
|
||||||
public Label wordMsgLabel;
|
|
||||||
public ProgressBar loadWordProgressBar;
|
|
||||||
public Button translateWordBtn;
|
|
||||||
public Button packBtn;
|
|
||||||
private final PBOUtil pboUtil = PBOUtil.getInstance();
|
|
||||||
private final TranslateUtil translateUtil = TranslateUtil.getInstance();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译标志 用于停止翻译
|
|
||||||
*/
|
|
||||||
private AtomicBoolean transTag;
|
|
||||||
/**
|
|
||||||
* 已翻译文本下标缓存
|
|
||||||
*/
|
|
||||||
private Set<Integer> transNum;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dragWindow() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VBox getRootPanel() {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRootFxml() {
|
|
||||||
return "main-view";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化数据
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initData() {
|
|
||||||
|
|
||||||
// 解包监听
|
|
||||||
pboUtil.setOnUnpackListener(new PBOUtil.OnUnpackListener() {
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
refreshWordBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUnpackSuccess(String unpackDirPath) {
|
|
||||||
loadFileLabel.textProperty().setValue("加载完成,正在获取文件目录");
|
|
||||||
// 展示解包文件内容
|
|
||||||
logger.info("正在获取文件目录。。");
|
|
||||||
showDirectory(new File(unpackDirPath));
|
|
||||||
// 展示可翻译语句
|
|
||||||
logger.info("正在查询待翻译文本目录。。");
|
|
||||||
showTranslateWord();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUnpackError(String msg) {
|
|
||||||
loadFileLabel.textProperty().setValue("打开文件失败");
|
|
||||||
logger.info("打开文件失败: \n" + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUnpackOver() {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打包监听
|
|
||||||
pboUtil.setOnPackListener(new PBOUtil.OnPackListener() {
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
showLoading("正在打包pbo文件");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProgress(long current, long all) {
|
|
||||||
showLoading(String.format("正在打包pbo文件(%d / %d)", current, all));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPackSuccess(File packFile) {
|
|
||||||
// 选择文件保存地址
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
|
||||||
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("PBO files (*.pbo)", "*.pbo");
|
|
||||||
fileChooser.getExtensionFilters().add(extFilter);
|
|
||||||
File file = fileChooser.showSaveDialog(getWindow());
|
|
||||||
if (file == null)
|
|
||||||
return;
|
|
||||||
if (file.exists()) {
|
|
||||||
//文件已存在,则删除覆盖文件
|
|
||||||
FileUtils.deleteQuietly(file);
|
|
||||||
}
|
|
||||||
String exportFilePath = file.getAbsolutePath();
|
|
||||||
logger.info("导出文件的路径 =>" + exportFilePath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
FileUtils.copyFile(packFile, file);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("保存文件失败!", e);
|
|
||||||
|
|
||||||
Platform.runLater(() -> AlertUtil.exception(e).content("保存文件失败!").show());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPackError(String msg) {
|
|
||||||
AlertUtil.error("保存文件失败!").show();
|
|
||||||
logger.info("保存文件失败: \n" + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPackOver() {
|
|
||||||
stopLoading();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取待翻译文字
|
|
||||||
pboUtil.setOnFindTransWordListener((words, isOver) -> {
|
|
||||||
loadWordProgressBar.setVisible(false);
|
|
||||||
|
|
||||||
if (words == null || words.isEmpty()) {
|
|
||||||
if (isOver) {
|
|
||||||
wordMsgLabel.textProperty().set("未找到待翻译文本");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 展示翻译按钮
|
|
||||||
translateWordBtn.setVisible(true);
|
|
||||||
// 展示打包按钮
|
|
||||||
packBtn.setVisible(true);
|
|
||||||
|
|
||||||
// 绑定TableView
|
|
||||||
boolean isCsvItem = (words.get(0) instanceof WordCsvItem);
|
|
||||||
bindWordTable(words, isCsvItem);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图样式
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initViewStyle() {
|
|
||||||
wordMsgLabel.textProperty().setValue("请打开PBO文件");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图事件
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initViewAction() {
|
|
||||||
// 翻译设置
|
|
||||||
translateSetupBtn.setOnAction(event -> open(SetupTranslateController.class, "翻译源设置"));
|
|
||||||
// 代理设置
|
|
||||||
proxySetupBtn.setOnAction(event -> open(SetupProxyController.class, "代理设置"));
|
|
||||||
|
|
||||||
// 选择pbo文件
|
|
||||||
EventHandler<ActionEvent> selectPboFileAction = actionEvent -> {
|
|
||||||
// 文件选择器
|
|
||||||
FileChooser fileChooser = new FileChooser();
|
|
||||||
FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("PBO files (*.pbo)", "*.pbo");
|
|
||||||
fileChooser.getExtensionFilters().add(extFilter);
|
|
||||||
selectFile(fileChooser.showOpenDialog(getWindow()));
|
|
||||||
};
|
|
||||||
openFileSetupBtn.setOnAction(selectPboFileAction);
|
|
||||||
openFile.setOnAction(selectPboFileAction);
|
|
||||||
|
|
||||||
// 拖拽效果 start ---------------------
|
|
||||||
fileBox.setOnDragEntered(dragEvent -> {
|
|
||||||
Dragboard dragboard = dragEvent.getDragboard();
|
|
||||||
if (dragboard.hasFiles() && isPboFile(dragboard.getFiles().get(0))) {
|
|
||||||
disableBox();
|
|
||||||
dragFileBox.setVisible(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fileBox.setOnDragExited(dragEvent -> {
|
|
||||||
if (!loadFileBox.isVisible()) {
|
|
||||||
disableBox();
|
|
||||||
openFileBox.setVisible(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fileBox.setOnDragOver(dragEvent -> {
|
|
||||||
Dragboard dragboard = dragEvent.getDragboard();
|
|
||||||
if (dragEvent.getGestureSource() != fileBox && dragboard.hasFiles()) {
|
|
||||||
/* allow for both copying and moving, whatever user chooses */
|
|
||||||
dragEvent.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
|
||||||
}
|
|
||||||
dragEvent.consume();
|
|
||||||
});
|
|
||||||
fileBox.setOnDragDropped(dragEvent -> {
|
|
||||||
disableBox();
|
|
||||||
openFileBox.setVisible(true);
|
|
||||||
|
|
||||||
Dragboard db = dragEvent.getDragboard();
|
|
||||||
boolean success = false;
|
|
||||||
File file = db.getFiles().get(0);
|
|
||||||
if (db.hasFiles() && isPboFile(file)) {
|
|
||||||
selectFile(file);
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
/* 让源知道字符串是否已成功传输和使用 */
|
|
||||||
dragEvent.setDropCompleted(success);
|
|
||||||
|
|
||||||
dragEvent.consume();
|
|
||||||
});
|
|
||||||
// 拖拽效果 end ---------------------
|
|
||||||
|
|
||||||
// 翻译按钮
|
|
||||||
translateWordBtn.setOnMouseClicked(mouseEvent -> {
|
|
||||||
|
|
||||||
// 是否初次翻译
|
|
||||||
if (transTag == null) {
|
|
||||||
transNum = new HashSet<>();
|
|
||||||
transTag = new AtomicBoolean(true);
|
|
||||||
// 开始翻译
|
|
||||||
startTranslate();
|
|
||||||
} else {
|
|
||||||
// 获取翻译列表
|
|
||||||
ObservableList<WordItem> items = wordTableBox.getItems();
|
|
||||||
// 未获取到翻译列表 或 翻译完成 则不做处理
|
|
||||||
if (items == null || items.isEmpty() || transNum.size() == items.size())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 设置翻译标识
|
|
||||||
transTag.set(!transTag.get());
|
|
||||||
|
|
||||||
if (Boolean.FALSE.equals(transTag.get())) {
|
|
||||||
stopTranslate();
|
|
||||||
} else {
|
|
||||||
startTranslate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// 打包按钮
|
|
||||||
packBtn.setOnAction(event -> pboUtil.pack(wordTableBox.getItems()));
|
|
||||||
|
|
||||||
// 复制文本
|
|
||||||
getRootPanel().getScene().getAccelerators()
|
|
||||||
.put(new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY), new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
TablePosition tablePosition = wordTableBox.getSelectionModel().getSelectedCells().get(0);
|
|
||||||
Object cellData = tablePosition.getTableColumn().getCellData(tablePosition.getRow());
|
|
||||||
ClipUtil.setClip(String.valueOf(cellData));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开始翻译
|
|
||||||
*/
|
|
||||||
private void startTranslate() {
|
|
||||||
// 获取翻译列表
|
|
||||||
ObservableList<WordItem> items = wordTableBox.getItems();
|
|
||||||
if (items == null || items.isEmpty()) return;
|
|
||||||
|
|
||||||
// 开始/继续 翻译
|
|
||||||
String label = translateWordBtn.getText().replaceAll("已暂停|一键翻译", "正在翻译");
|
|
||||||
translateWordBtn.textProperty().setValue(label);
|
|
||||||
|
|
||||||
// 禁用打包按钮
|
|
||||||
packBtn.setDisable(true);
|
|
||||||
|
|
||||||
boolean isCsvItem = (items.get(0) instanceof WordCsvItem);
|
|
||||||
// 循环提交翻译任务
|
|
||||||
for (int i = 0; i < items.size(); i++) {
|
|
||||||
// 跳过已翻译文本
|
|
||||||
if (transNum.contains(i)) continue;
|
|
||||||
|
|
||||||
WordItem item = items.get(i);
|
|
||||||
|
|
||||||
// 提交翻译任务
|
|
||||||
int finalI = i;
|
|
||||||
translateUtil.translate(finalI, item.getOriginal(), new TranslateUtil.OnTranslateListener() {
|
|
||||||
@Override
|
|
||||||
public void onTranslate(String result) {
|
|
||||||
// 防止多线程执行时停止不及时
|
|
||||||
if (Boolean.FALSE.equals(transTag.get())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 含有中文则不翻译
|
|
||||||
if (!containsChinese(item.getChinese()))
|
|
||||||
item.setChinese(result);
|
|
||||||
|
|
||||||
// 设置简中文本
|
|
||||||
if (isCsvItem) {
|
|
||||||
WordCsvItem csvItem = ((WordCsvItem) item);
|
|
||||||
if (!containsChinese(csvItem.getChineseSimp()))
|
|
||||||
csvItem.setChineseSimp(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置翻译进度
|
|
||||||
transNum.add(finalI);
|
|
||||||
String label;
|
|
||||||
if (transNum.size() >= items.size()) {
|
|
||||||
label = "翻译完成(" + items.size() + ")";
|
|
||||||
transTag.set(false);
|
|
||||||
// 启用打包按钮
|
|
||||||
packBtn.setDisable(false);
|
|
||||||
} else {
|
|
||||||
label = "正在翻译(" + transNum.size() + "/" + items.size() + ")";
|
|
||||||
}
|
|
||||||
translateWordBtn.textProperty().setValue(label);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止翻译
|
|
||||||
*/
|
|
||||||
private void stopTranslate() {
|
|
||||||
// 清除未完成的翻译任务
|
|
||||||
translateUtil.clear();
|
|
||||||
// 设置翻译状态
|
|
||||||
String label = translateWordBtn.getText().replace("正在翻译", "已暂停");
|
|
||||||
translateWordBtn.textProperty().setValue(label);
|
|
||||||
// 启用打包按钮
|
|
||||||
packBtn.setDisable(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 选择待汉化pbo文件
|
|
||||||
* <p>TODO 多文件汉化
|
|
||||||
*
|
|
||||||
* @param file 待汉化文件
|
|
||||||
*/
|
|
||||||
private void selectFile(File file) {
|
|
||||||
if (file == null || !file.exists()) return;
|
|
||||||
|
|
||||||
filePath.textProperty().set(file.getName());
|
|
||||||
|
|
||||||
// 重置文件界面
|
|
||||||
disableBox();
|
|
||||||
loadFileBox.setVisible(true);
|
|
||||||
// 重置翻译文本状态
|
|
||||||
wordBox.getChildren().remove(wordTableBox);
|
|
||||||
wordTableBox = null;
|
|
||||||
|
|
||||||
loadFileLabel.textProperty().setValue("正在加载模组文件");
|
|
||||||
pboUtil.unpack(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 展示文件夹内容
|
|
||||||
*
|
|
||||||
* @param file 根目录
|
|
||||||
*/
|
|
||||||
private void showDirectory(File file) {
|
|
||||||
if (file == null || !file.exists() || !file.isDirectory()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
disableBox();
|
|
||||||
treeFileBox.setVisible(true);
|
|
||||||
|
|
||||||
// 加载pbo文件目录
|
|
||||||
FileTreeItem fileTreeItem = new FileTreeItem(file, File::listFiles);
|
|
||||||
treeFileBox.setRoot(fileTreeItem);
|
|
||||||
treeFileBox.setShowRoot(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 展示待翻译语句
|
|
||||||
*/
|
|
||||||
private void showTranslateWord() {
|
|
||||||
wordMsgLabel.textProperty().setValue("正在获取可翻译文本");
|
|
||||||
loadWordProgressBar.setVisible(true);
|
|
||||||
|
|
||||||
pboUtil.startFindWord();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绑定表格数据
|
|
||||||
*
|
|
||||||
* @param words 单词列表
|
|
||||||
* @param isCsvItem 是否csv
|
|
||||||
*/
|
|
||||||
private void bindWordTable(List<WordItem> words, boolean isCsvItem) {
|
|
||||||
if (wordTableBox == null) {
|
|
||||||
wordTableBox = new TableView<>();
|
|
||||||
wordBox.getChildren().add(wordTableBox);
|
|
||||||
// 可编辑
|
|
||||||
wordTableBox.setEditable(true);
|
|
||||||
// 单元格选择模式而不是行选择
|
|
||||||
wordTableBox.getSelectionModel().setCellSelectionEnabled(true);
|
|
||||||
// 不允许选择多个单元格
|
|
||||||
wordTableBox.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
|
||||||
// 鼠标事件清空
|
|
||||||
wordTableBox.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
|
||||||
if (event.isControlDown()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wordTableBox.getEditingCell() == null) {
|
|
||||||
wordTableBox.getSelectionModel().clearSelection();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建列
|
|
||||||
wordTableBox.getColumns().add(createColumn("原始文本", WordItem::originalProperty));
|
|
||||||
wordTableBox.getColumns().add(createColumn("中文", WordItem::chineseProperty));
|
|
||||||
|
|
||||||
if (isCsvItem) {
|
|
||||||
wordTableBox.getColumns().add(createColumn("简体中文", WordCsvItem::chineseSimpProperty));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加表数据
|
|
||||||
wordTableBox.getItems().addAll(words);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends WordItem> TableColumn<WordItem, String> createColumn(String colName, Function<T, StringProperty> colField) {
|
|
||||||
TableColumn<WordItem, String> tableColumn = new TableColumn<>(colName);
|
|
||||||
tableColumn.setCellValueFactory(features -> colField.apply((T) features.getValue()));
|
|
||||||
tableColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
|
||||||
tableColumn.setPrefWidth(150);
|
|
||||||
tableColumn.setSortable(false);
|
|
||||||
tableColumn.setEditable(!"原始文本".equals(colName));
|
|
||||||
return tableColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void disableBox() {
|
|
||||||
openFileBox.setVisible(false);
|
|
||||||
dragFileBox.setVisible(false);
|
|
||||||
loadFileBox.setVisible(false);
|
|
||||||
treeFileBox.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshWordBox() {
|
|
||||||
if (wordTableBox != null) {
|
|
||||||
wordBox.getChildren().remove(wordTableBox);
|
|
||||||
wordTableBox = null;
|
|
||||||
}
|
|
||||||
wordMsgLabel.textProperty().setValue("请打开pbo文件");
|
|
||||||
loadWordProgressBar.setVisible(false);
|
|
||||||
translateWordBtn.textProperty().setValue("一键翻译");
|
|
||||||
translateWordBtn.setVisible(false);
|
|
||||||
packBtn.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPboFile(File file) {
|
|
||||||
if (file == null) return false;
|
|
||||||
return Pattern.compile(".*(.pbo)$").matcher(file.getName()).matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 给定字符串是否含有中文
|
|
||||||
*
|
|
||||||
* @param str 需要判断的字符串
|
|
||||||
* @return 是否含有中文
|
|
||||||
*/
|
|
||||||
private boolean containsChinese(String str) {
|
|
||||||
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,162 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.controller;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.base.BaseController;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.FxmlUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpUtil;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.lang3.math.NumberUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用配置
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class SetupProxyController extends BaseController<StackPane> {
|
|
||||||
public StackPane root;
|
|
||||||
public TextField hostField;
|
|
||||||
public TextField portField;
|
|
||||||
public TextField testPath;
|
|
||||||
public static final String PROXY_ERROR = "ProxyError";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 窗口拖拽设置
|
|
||||||
*
|
|
||||||
* @return 是否启用
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean dragWindow() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取根布局
|
|
||||||
*
|
|
||||||
* @return 根布局对象
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public StackPane getRootPanel() {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取根布局
|
|
||||||
* <p> 搭配 <code>FxmlUtil.load</code> 使用
|
|
||||||
*
|
|
||||||
* @return 根布局对象
|
|
||||||
* @see FxmlUtil#load(String)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getRootFxml() {
|
|
||||||
return "proxy-view";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化数据
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initData() {
|
|
||||||
// 是否已有代理配置
|
|
||||||
if (CustomConfig.hasProxy()) {
|
|
||||||
hostField.textProperty().setValue(CustomConfig.proxyHost());
|
|
||||||
portField.textProperty().setValue(String.valueOf(CustomConfig.proxyPort()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认测试地址
|
|
||||||
testPath.textProperty().setValue("https://translate.googleapis.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图样式
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initViewStyle() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图事件
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initViewAction() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getHost() {
|
|
||||||
String text = hostField.getText();
|
|
||||||
if (StringUtils.isBlank(text)) {
|
|
||||||
throw new RuntimeException(PROXY_ERROR);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
URI.create(text);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(PROXY_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getPort() {
|
|
||||||
String text = portField.getText();
|
|
||||||
if (StringUtils.isBlank(text)) {
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean creatable = NumberUtils.isCreatable(text);
|
|
||||||
if (!creatable) {
|
|
||||||
throw new RuntimeException(PROXY_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Integer.parseInt(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTestPath() {
|
|
||||||
String text = testPath.getText();
|
|
||||||
if (StringUtils.isBlank(text)) {
|
|
||||||
throw new RuntimeException(PROXY_ERROR);
|
|
||||||
}
|
|
||||||
return (text.startsWith("http") ? "" : "http://") + text;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试代理有效性
|
|
||||||
*/
|
|
||||||
public void test() {
|
|
||||||
HttpUtil.getInstance().clearProxy();
|
|
||||||
try {
|
|
||||||
String resp = HttpUtil.getInstance().proxy(getHost(), getPort())
|
|
||||||
.get(getTestPath(), null, null);
|
|
||||||
AlertUtil.info("成功").show();
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
logger.error("代理访问失败", e);
|
|
||||||
AlertUtil.error("失败!").show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存代理配置
|
|
||||||
*/
|
|
||||||
public void save() {
|
|
||||||
CustomConfig.proxyHost(getHost());
|
|
||||||
CustomConfig.proxyPort(getPort());
|
|
||||||
|
|
||||||
CustomConfig.store();
|
|
||||||
|
|
||||||
onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取消
|
|
||||||
*/
|
|
||||||
public void close() {
|
|
||||||
HttpUtil.getInstance().clearProxy();
|
|
||||||
|
|
||||||
onDestroy();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.controller;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.base.BaseController;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.scene.control.ComboBox;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.util.StringConverter;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译设置控制器
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class SetupTranslateController extends BaseController<StackPane> {
|
|
||||||
public StackPane root;
|
|
||||||
public ComboBox<TranslateSource> translateSourceCombo;
|
|
||||||
public TextField qps;
|
|
||||||
public VBox appidBox;
|
|
||||||
public TextField appid;
|
|
||||||
public VBox apikeyBox;
|
|
||||||
public TextField apikey;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dragWindow() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public StackPane getRootPanel() {
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRootFxml() {
|
|
||||||
return "translate-view";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化数据
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initData() {
|
|
||||||
// 翻译源
|
|
||||||
for (TranslateSource value : TranslateSource.values()) {
|
|
||||||
ObservableList<TranslateSource> items = translateSourceCombo.getItems();
|
|
||||||
items.addAll(value);
|
|
||||||
}
|
|
||||||
translateSourceCombo.setConverter(new StringConverter<>() {
|
|
||||||
@Override
|
|
||||||
public String toString(TranslateSource object) {
|
|
||||||
if (object == null) return null;
|
|
||||||
|
|
||||||
return object.getLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TranslateSource fromString(String string) {
|
|
||||||
return TranslateSource.getByLabel(string);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
translateSourceCombo.getSelectionModel()
|
|
||||||
.selectedItemProperty()
|
|
||||||
.addListener((observable, oldValue, newValue) -> {
|
|
||||||
boolean needApiKey = newValue.needApiKey();
|
|
||||||
appidBox.setVisible(needApiKey);
|
|
||||||
apikeyBox.setVisible(needApiKey);
|
|
||||||
if (needApiKey) {
|
|
||||||
appid.textProperty().setValue(CustomConfig.translateSourceAppid(newValue));
|
|
||||||
apikey.textProperty().setValue(CustomConfig.translateSourceApikey(newValue));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
qps.textProperty().setValue(String.valueOf(CustomConfig.translateSourceQps(newValue)));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 当前翻译源
|
|
||||||
translateSourceCombo.getSelectionModel().select(CustomConfig.translateSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图样式
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initViewStyle() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图事件
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initViewAction() {
|
|
||||||
qps.textProperty().addListener((observable, oldValue, newValue) -> {
|
|
||||||
if (!newValue.matches("\\d*")) {
|
|
||||||
qps.setText(oldValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
TranslateSource source = translateSourceCombo.getValue();
|
|
||||||
String apikey = this.apikey.getText();
|
|
||||||
String appid = this.appid.getText();
|
|
||||||
int qps = Integer.parseInt(this.qps.getText());
|
|
||||||
|
|
||||||
CustomConfig.translateSource(source);
|
|
||||||
if (source.needApiKey()) {
|
|
||||||
if (StringUtils.isBlank(apikey) || StringUtils.isBlank(appid)) {
|
|
||||||
AlertUtil.error("认证信息不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomConfig.translateSourceApikey(source, apikey);
|
|
||||||
CustomConfig.translateSourceAppid(source, appid);
|
|
||||||
CustomConfig.translateSourceQps(source, qps);
|
|
||||||
}
|
|
||||||
// 保存到文件
|
|
||||||
CustomConfig.store();
|
|
||||||
// 退出
|
|
||||||
onDestroy();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.FileUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.ProcessesUtil;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cfg文件转换工具类
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class CfgConvertUtil {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(CfgConvertUtil.class);
|
|
||||||
private static CfgConvertUtil util;
|
|
||||||
private static final String CfgConvert_DIR_PATH = AppConstant.DATA_DIR_PATH + File.separator + "CfgConvert";
|
|
||||||
private static final File CfgConvert_DIR = new File(CfgConvert_DIR_PATH);
|
|
||||||
private static final String CfgConvert_FILE_PATH = CfgConvert_DIR_PATH + File.separator + "CfgConvert.exe";
|
|
||||||
private static final File CfgConvert_FILE = new File(CfgConvert_FILE_PATH);
|
|
||||||
private static final String COMMAND = CfgConvert_FILE_PATH + " %s -dst %s %s";
|
|
||||||
|
|
||||||
private CfgConvertUtil() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void init() {
|
|
||||||
if (util == null) {
|
|
||||||
util = new CfgConvertUtil();
|
|
||||||
}
|
|
||||||
// 检查pbo解析文件
|
|
||||||
util.checkCfgConvert();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized CfgConvertUtil getInstance() {
|
|
||||||
if (util == null)
|
|
||||||
throw new RuntimeException("are you ready ?");
|
|
||||||
return util;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查Cfg转换文件
|
|
||||||
*/
|
|
||||||
private void checkCfgConvert() {
|
|
||||||
if (!CfgConvert_FILE.exists()) initCfgConvert();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initCfgConvert() {
|
|
||||||
try {
|
|
||||||
FileUtils.forceMkdir(CfgConvert_DIR);
|
|
||||||
String cfgConvertFileName = "CfgConvert.exe";
|
|
||||||
FileUtil.copyFile(Objects.requireNonNull(CfgConvertUtil.class.getResourceAsStream("/static/CfgConvert/" + cfgConvertFileName)), new File(CfgConvert_DIR_PATH + File.separator + cfgConvertFileName));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toTxt(File binFile, String outPath, OnToTxtListener onToTxtListener) {
|
|
||||||
String fileName = binFile.getAbsolutePath();
|
|
||||||
ProcessesUtil.exec(toTxtCommand(binFile, outPath), new ProcessesUtil.OnExecuteListener() {
|
|
||||||
@Override
|
|
||||||
public void onExecute(String msg) {
|
|
||||||
logger.info(fileName + " : " + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteSuccess(int exitValue) {
|
|
||||||
logger.info(fileName + " : to txt success");
|
|
||||||
String outFilePath = outFilePath(binFile, outPath, ".cpp");
|
|
||||||
if (onToTxtListener != null) {
|
|
||||||
onToTxtListener.onToTxtSuccess(outFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteError(Exception e) {
|
|
||||||
logger.error(fileName + " : to txt error", e);
|
|
||||||
if (onToTxtListener != null) {
|
|
||||||
onToTxtListener.onToTxtError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteOver() {
|
|
||||||
logger.info(fileName + " : to txt end...");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toBin(File cppFile) {
|
|
||||||
ProcessesUtil.exec(toBinCommand(cppFile, cppFile.getParentFile().getAbsolutePath()), new ProcessesUtil.OnExecuteListener() {
|
|
||||||
@Override
|
|
||||||
public void onExecute(String msg) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteSuccess(int exitValue) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteError(Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteOver() {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toBinCommand(File cppFile, String outPath) {
|
|
||||||
String outFilePath = outFilePath(cppFile, outPath, ".bin");
|
|
||||||
return String.format(COMMAND, "-bin", outFilePath, cppFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toTxtCommand(File binFile, String outPath) {
|
|
||||||
String outFilePath = outFilePath(binFile, outPath, ".cpp");
|
|
||||||
return String.format(COMMAND, "-txt", outFilePath, binFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String outFilePath(File file, String outPath, String suffix) {
|
|
||||||
return outPath + File.separator + FileUtil.mainName(file) + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnToTxtListener {
|
|
||||||
void onToTxtSuccess(String txtFilePath);
|
|
||||||
|
|
||||||
void onToTxtError(Exception e);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,628 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.AppConstant;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadPoolManager;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordCsvItem;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.word.WordItem;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.AlertUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.FileUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.util.ProcessesUtil;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.io.LineIterator;
|
|
||||||
import org.apache.commons.io.filefilter.NameFileFilter;
|
|
||||||
import org.apache.commons.io.filefilter.TrueFileFilter;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PBO 工具类
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
* @see <a href="https://github.com/winseros/pboman3">https://github.com/winseros/pboman3</a>
|
|
||||||
*/
|
|
||||||
public class PBOUtil {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PBOUtil.class);
|
|
||||||
private static PBOUtil util;
|
|
||||||
private static final String PBOC_DIR_PATH = AppConstant.DATA_DIR_PATH + File.separator + "pboman";
|
|
||||||
private static final File PBOC_DIR = new File(PBOC_DIR_PATH);
|
|
||||||
private static final String PBOC_FILE_PATH = PBOC_DIR_PATH + File.separator + "pboc.exe";
|
|
||||||
private static final File PBOC_FILE = new File(PBOC_FILE_PATH);
|
|
||||||
private static final String UNPACK_COMMAND = PBOC_FILE_PATH + " unpack -o " + AppConstant.TMP_DIR_PATH + " %s";
|
|
||||||
private static final String PACK_COMMAND = PBOC_FILE_PATH + " pack -o %s %s";
|
|
||||||
private OnPackListener onPackListener;
|
|
||||||
private OnUnpackListener onUnpackListener;
|
|
||||||
private OnFindTransWordListener onFindTransWordListener;
|
|
||||||
private String unpackPath;
|
|
||||||
private CfgConvertUtil cfgConvertUtil;
|
|
||||||
private static final String FILE_NAME_STRING_TABLE = "stringtable.csv";
|
|
||||||
private static final String FILE_NAME_CONFIG_BIN = "config.bin";
|
|
||||||
private static final String FILE_NAME_CONFIG_CPP = "config.cpp";
|
|
||||||
|
|
||||||
private PBOUtil() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void init(CfgConvertUtil cfgConvertUtil) {
|
|
||||||
if (util == null) {
|
|
||||||
util = new PBOUtil();
|
|
||||||
}
|
|
||||||
// cfg转换工具
|
|
||||||
util.cfgConvertUtil = cfgConvertUtil;
|
|
||||||
// 检查pbo解析文件
|
|
||||||
util.checkPboc();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized PBOUtil getInstance() {
|
|
||||||
if (util == null)
|
|
||||||
throw new RuntimeException("are you ready ?");
|
|
||||||
return util;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置打包监听器
|
|
||||||
*
|
|
||||||
* @param onPackListener 打包监听器
|
|
||||||
*/
|
|
||||||
public void setOnPackListener(OnPackListener onPackListener) {
|
|
||||||
this.onPackListener = onPackListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置解包监听器
|
|
||||||
*
|
|
||||||
* @param onUnpackListener 监听器
|
|
||||||
*/
|
|
||||||
public void setOnUnpackListener(OnUnpackListener onUnpackListener) {
|
|
||||||
this.onUnpackListener = onUnpackListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnFindTransWordListener(OnFindTransWordListener onFindTransWordListener) {
|
|
||||||
this.onFindTransWordListener = onFindTransWordListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkPboc() {
|
|
||||||
if (!PBOC_FILE.exists()) initPboc();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initPboc() {
|
|
||||||
try {
|
|
||||||
FileUtils.forceMkdir(PBOC_DIR);
|
|
||||||
String pbocFileName = "pboc.exe";
|
|
||||||
String dllFileName = "Qt6Core.dll";
|
|
||||||
FileUtil.copyFile(Objects.requireNonNull(PBOUtil.class.getResourceAsStream("/static/pboc/" + pbocFileName)), new File(PBOC_DIR_PATH + File.separator + pbocFileName));
|
|
||||||
FileUtil.copyFile(Objects.requireNonNull(PBOUtil.class.getResourceAsStream("/static/pboc/" + dllFileName)), new File(PBOC_DIR_PATH + File.separator + dllFileName));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解压PBO文件
|
|
||||||
*
|
|
||||||
* @param file PBO文件
|
|
||||||
*/
|
|
||||||
public void unpack(File file) {
|
|
||||||
// 检查pbo解包程序
|
|
||||||
checkPboc();
|
|
||||||
// 清理缓存
|
|
||||||
clear();
|
|
||||||
|
|
||||||
if (onUnpackListener != null) {
|
|
||||||
onUnpackListener.onStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
String filePath = file.getAbsolutePath();
|
|
||||||
if (filePath.contains(" ")) filePath = "\"" + filePath + "\"";
|
|
||||||
|
|
||||||
String command = String.format(UNPACK_COMMAND, filePath);
|
|
||||||
logger.info(command);
|
|
||||||
try {
|
|
||||||
FileUtils.forceMkdir(new File(AppConstant.TMP_DIR_PATH));
|
|
||||||
|
|
||||||
// 执行命令
|
|
||||||
ProcessesUtil.exec(command, new ProcessesUtil.OnExecuteListener() {
|
|
||||||
@Override
|
|
||||||
public void onExecute(String msg) {
|
|
||||||
logger.info(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteSuccess(int exitValue) {
|
|
||||||
if (exitValue != 0) {
|
|
||||||
String msg = "打开PBO文件失败!";
|
|
||||||
logger.error(msg);
|
|
||||||
if (onUnpackListener != null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
onUnpackListener.onUnpackError(msg);
|
|
||||||
onUnpackListener.onUnpackOver();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.info("打开PBO文件成功!");
|
|
||||||
unpackPath = AppConstant.TMP_DIR_PATH + File.separator + FileUtil.mainName(file);
|
|
||||||
if (onUnpackListener != null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
onUnpackListener.onUnpackSuccess(unpackPath);
|
|
||||||
onUnpackListener.onUnpackOver();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteError(Exception e) {
|
|
||||||
logger.error("", e);
|
|
||||||
if (onUnpackListener != null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
onUnpackListener.onUnpackError(e.getMessage());
|
|
||||||
onUnpackListener.onUnpackOver();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteOver() {
|
|
||||||
if (onUnpackListener != null) {
|
|
||||||
Platform.runLater(() -> onUnpackListener.onUnpackOver());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("", e);
|
|
||||||
if (onUnpackListener != null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
onUnpackListener.onUnpackError(e.getMessage());
|
|
||||||
onUnpackListener.onUnpackOver();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取待翻译单词列表
|
|
||||||
*/
|
|
||||||
public void startFindWord() {
|
|
||||||
// 检查pbo解包文件
|
|
||||||
if (unpackPath == null || StringUtils.isBlank(unpackPath))
|
|
||||||
throw new RuntimeException("No PBO file was obtained !");
|
|
||||||
|
|
||||||
ThreadPoolManager.getInstance().execute(() -> {
|
|
||||||
if (hasStringTable()) {
|
|
||||||
List<WordItem> worlds = new ArrayList<>(readCsvFile());
|
|
||||||
if (onFindTransWordListener != null) {
|
|
||||||
Platform.runLater(() -> onFindTransWordListener.onFoundWords(worlds, true));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
findConfigWord();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取csv中 原文及 简中单词
|
|
||||||
*
|
|
||||||
* @return 待翻译语句列表
|
|
||||||
*/
|
|
||||||
private List<WordCsvItem> readCsvFile() {
|
|
||||||
List<WordCsvItem> list = new ArrayList<>();
|
|
||||||
AtomicInteger position = new AtomicInteger(0);
|
|
||||||
File stringTable = new File(unpackPath + File.separator + FILE_NAME_STRING_TABLE);
|
|
||||||
try (LineIterator it = FileUtils.lineIterator(stringTable, StandardCharsets.UTF_8.name())) {
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String line = it.nextLine();
|
|
||||||
|
|
||||||
if (line.isEmpty() || line.startsWith("//") || line.startsWith("\"Language\"")) {
|
|
||||||
position.addAndGet(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 原句
|
|
||||||
int startIndex = line.indexOf(",\"") + 2;
|
|
||||||
int endIndex = line.indexOf("\"", startIndex);
|
|
||||||
String original = line.substring(startIndex, endIndex);
|
|
||||||
|
|
||||||
// 中文
|
|
||||||
startIndex = StringUtils.ordinalIndexOf(line, ",\"", 11) + 2;
|
|
||||||
endIndex = line.indexOf("\"", startIndex);
|
|
||||||
int[] chinesePosition = new int[]{startIndex, endIndex};
|
|
||||||
String chinese = line.substring(startIndex, endIndex);
|
|
||||||
|
|
||||||
// 简中
|
|
||||||
startIndex = StringUtils.ordinalIndexOf(line, ",\"", 14) + 2;
|
|
||||||
endIndex = line.indexOf("\"", startIndex);
|
|
||||||
int[] chineseSimpPosition = new int[]{startIndex, endIndex};
|
|
||||||
String chineseSimp = line.substring(startIndex, endIndex);
|
|
||||||
|
|
||||||
// 添加单词
|
|
||||||
list.add(new WordCsvItem(stringTable, position.get(), original, chinese, chinesePosition, chineseSimp, chineseSimpPosition));
|
|
||||||
|
|
||||||
position.addAndGet(1);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
list.sort(Comparator.comparingInt(WordItem::getLines));
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有 config.bin 文件内 可翻译内容
|
|
||||||
*/
|
|
||||||
private void findConfigWord() {
|
|
||||||
|
|
||||||
// 搜索所有的 config.bin 文件,并按路径解包到bak文件夹
|
|
||||||
List<File> files = new ArrayList<>(FileUtils.listFiles(new File(unpackPath), new NameFileFilter(FILE_NAME_CONFIG_BIN, FILE_NAME_CONFIG_CPP), TrueFileFilter.INSTANCE));
|
|
||||||
|
|
||||||
files.forEach(file -> {
|
|
||||||
|
|
||||||
// 转换bin文件为cpp可读取文件
|
|
||||||
if (file.getName().endsWith("bin")) {
|
|
||||||
cfgConvertUtil.toTxt(file, file.getParentFile().getAbsolutePath(), new CfgConvertUtil.OnToTxtListener() {
|
|
||||||
@Override
|
|
||||||
public void onToTxtSuccess(String txtFilePath) {
|
|
||||||
// 读取 cpp 文件
|
|
||||||
readCppFile(new File(txtFilePath), file.equals(files.get(files.size() - 1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onToTxtError(Exception e) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
AlertUtil.exception(e).content(FILE_NAME_CONFIG_BIN + "文件转换失败").show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 读取 cpp 文件
|
|
||||||
readCppFile(file, file.equals(files.get(files.size() - 1)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static final Pattern pattern = Pattern.compile(".*((displayName|descriptionShort).?=.?\").*");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取cpp文件,查询可翻译文本
|
|
||||||
*
|
|
||||||
* @param file cpp文件
|
|
||||||
*/
|
|
||||||
private void readCppFile(File file, boolean isEnd) {
|
|
||||||
List<WordItem> list = new ArrayList<>();
|
|
||||||
AtomicInteger lines = new AtomicInteger(0);
|
|
||||||
try (LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name())) {
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String line = it.nextLine();
|
|
||||||
|
|
||||||
Matcher matcher = pattern.matcher(line);
|
|
||||||
if (!line.contains("$") && matcher.find()) {
|
|
||||||
|
|
||||||
String name = matcher.group(1);
|
|
||||||
|
|
||||||
// 原始文本
|
|
||||||
int startIndex = line.indexOf(name) + name.length();
|
|
||||||
|
|
||||||
int endIndex = line.indexOf("\"", startIndex);
|
|
||||||
String original;
|
|
||||||
try {
|
|
||||||
original = line.substring(startIndex, endIndex);
|
|
||||||
} catch (Exception e) {
|
|
||||||
lines.addAndGet(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加单词
|
|
||||||
if (!"".endsWith(original) && !containsChinese(original)) {
|
|
||||||
list.add(new WordItem(file, lines.get(), original, "", new int[]{startIndex, endIndex}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.addAndGet(1);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("", e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
list.sort(Comparator.comparingInt(WordItem::getLines));
|
|
||||||
|
|
||||||
if (onFindTransWordListener != null) {
|
|
||||||
Platform.runLater(() -> onFindTransWordListener.onFoundWords(list, isEnd));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 给定字符串是否存在中文
|
|
||||||
*
|
|
||||||
* @param str 字符串
|
|
||||||
* @return 是否存在中文
|
|
||||||
*/
|
|
||||||
private boolean containsChinese(String str) {
|
|
||||||
// [\u4e00-\u9fa5]
|
|
||||||
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打包PBO文件
|
|
||||||
*/
|
|
||||||
public void pack(List<WordItem> words) {
|
|
||||||
if (onPackListener != null) {
|
|
||||||
Platform.runLater(() -> onPackListener.onStart());
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadPoolManager.getInstance().execute(() -> {
|
|
||||||
File unpackDir;
|
|
||||||
if (StringUtils.isBlank(unpackPath)
|
|
||||||
|| !(unpackDir = new File(unpackPath)).exists()
|
|
||||||
|| !unpackDir.isDirectory()
|
|
||||||
) {
|
|
||||||
AlertUtil.error("未获取到打开的pbo文件!").show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入翻译后文本
|
|
||||||
try {
|
|
||||||
writeWords(words);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("writeWords error", e);
|
|
||||||
if (onPackListener != null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
onPackListener.onPackOver();
|
|
||||||
onPackListener.onPackError("writeWords error ==> " + e.getMessage());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打包文件临时保存路径
|
|
||||||
String packFilePath = unpackPath + ".pbo";
|
|
||||||
File packFile = new File(packFilePath);
|
|
||||||
if (packFile.exists()) {
|
|
||||||
// 如果存在则删除
|
|
||||||
FileUtils.deleteQuietly(packFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行打包指令
|
|
||||||
String command = String.format(PACK_COMMAND, AppConstant.TMP_DIR_PATH, unpackPath);
|
|
||||||
logger.info(command);
|
|
||||||
ProcessesUtil.exec(command, new ProcessesUtil.OnExecuteListener() {
|
|
||||||
@Override
|
|
||||||
public void onExecute(String msg) {
|
|
||||||
logger.info(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteSuccess(int exitValue) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
if (exitValue != 0) {
|
|
||||||
logger.error("保存PBO文件失败!");
|
|
||||||
if (onPackListener != null) {
|
|
||||||
onPackListener.onPackOver();
|
|
||||||
onPackListener.onPackError("保存PBO文件失败!");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (onPackListener != null) {
|
|
||||||
onPackListener.onPackOver();
|
|
||||||
onPackListener.onPackSuccess(packFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteError(Exception e) {
|
|
||||||
logger.error("保存PBO文件失败!");
|
|
||||||
if (onPackListener != null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
onPackListener.onPackOver();
|
|
||||||
onPackListener.onPackError(e.getMessage());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteOver() {
|
|
||||||
if (onPackListener != null) {
|
|
||||||
Platform.runLater(() -> onPackListener.onPackOver());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 写入翻译文本
|
|
||||||
*
|
|
||||||
* @param words 已经翻译好的文本对象
|
|
||||||
*/
|
|
||||||
private void writeWords(List<WordItem> words) throws Exception {
|
|
||||||
Map<File, List<WordItem>> wordMap = words.stream()
|
|
||||||
.collect(Collectors.groupingBy(WordItem::getFile, Collectors.toList()));
|
|
||||||
|
|
||||||
AtomicInteger progress = new AtomicInteger(0);
|
|
||||||
|
|
||||||
// 0 执行成功 大于0 执行失败
|
|
||||||
List<Future<Integer>> result = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Map.Entry<File, List<WordItem>> entry : wordMap.entrySet()) {
|
|
||||||
Future<Integer> submit = ThreadPoolManager.getInstance().submit(() -> {
|
|
||||||
try {
|
|
||||||
entry.getValue().sort(Comparator.comparingInt(WordItem::getLines));
|
|
||||||
|
|
||||||
File file = entry.getKey();
|
|
||||||
// 创建备份文件
|
|
||||||
File bakFile = getWordBakFile(file);
|
|
||||||
// 判断重复打包时 备份文件处理
|
|
||||||
if (!bakFile.exists()) {
|
|
||||||
FileUtils.copyFile(file, bakFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空原始文件
|
|
||||||
FileWriter fileWriter = new FileWriter(file);
|
|
||||||
fileWriter.write("");
|
|
||||||
fileWriter.flush();
|
|
||||||
fileWriter.close();
|
|
||||||
|
|
||||||
// 遍历拼接翻译文本并写入
|
|
||||||
long lines = 0;
|
|
||||||
String line;
|
|
||||||
LineIterator it = FileUtils.lineIterator(bakFile, StandardCharsets.UTF_8.name());
|
|
||||||
while (it.hasNext() && !entry.getValue().isEmpty()) {
|
|
||||||
line = it.nextLine();
|
|
||||||
|
|
||||||
WordItem item = entry.getValue().get(0);
|
|
||||||
int[] ip = item.getPosition();
|
|
||||||
|
|
||||||
// 拼接翻译后的文本
|
|
||||||
if (lines == item.getLines()) {
|
|
||||||
if (item instanceof WordCsvItem csv) {
|
|
||||||
int[] simp = csv.getPositionSimp();
|
|
||||||
// 判断 ip 是否比 simp 靠前
|
|
||||||
boolean tag = ip[0] < simp[0];
|
|
||||||
line = line.substring(0, (tag ? ip : simp)[0])
|
|
||||||
+ (tag ? csv.getChinese() : csv.getChineseSimp())
|
|
||||||
+ line.substring((tag ? ip : simp)[1], (tag ? simp : ip)[0])
|
|
||||||
+ (tag ? csv.getChineseSimp() : csv.getChinese())
|
|
||||||
+ line.substring((tag ? simp : ip)[1]);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
line = line.substring(0, ip[0])
|
|
||||||
+ item.getChinese()
|
|
||||||
+ line.substring(ip[1]);
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.out.println(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.getValue().remove(item);
|
|
||||||
if (onPackListener != null) {
|
|
||||||
Platform.runLater(() -> onPackListener.onProgress(progress.addAndGet(1), words.size()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入原始文件
|
|
||||||
FileUtils.writeStringToFile(file, line + System.lineSeparator(), StandardCharsets.UTF_8, true);
|
|
||||||
|
|
||||||
lines++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭流
|
|
||||||
IOUtils.closeQuietly(it);
|
|
||||||
|
|
||||||
// cpp 文件需要 转换为 bin
|
|
||||||
if (file.getName().endsWith("cpp")) {
|
|
||||||
File binFile = new File(file.getParent() + File.separator + FileUtil.mainName(file) + ".bin");
|
|
||||||
if (binFile.exists()) {
|
|
||||||
// 转为bin文件
|
|
||||||
cfgConvertUtil.toBin(file);
|
|
||||||
// 删除cpp文件
|
|
||||||
FileUtils.deleteQuietly(file);
|
|
||||||
}
|
|
||||||
// 同目录下不存在bin文件,说明原始文件为cpp 无需转换
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("写入翻译文本失败", e);
|
|
||||||
// 执行失败
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// 执行成功
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加执行结果
|
|
||||||
result.add(submit);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Future<Integer> future : result) {
|
|
||||||
if (future.get() > 0)
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File getWordBakFile(File wordFile) {
|
|
||||||
return new File(wordFile.getAbsolutePath().replace(unpackPath, AppConstant.BAK_FILE_PATH));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否有国际化翻译文件
|
|
||||||
*
|
|
||||||
* @return 是否有 <code>stringtable.csv</code> 文件
|
|
||||||
*/
|
|
||||||
private boolean hasStringTable() {
|
|
||||||
List<String> fileNames = FileUtil.listFileNames(unpackPath);
|
|
||||||
return fileNames.stream().anyMatch(FILE_NAME_STRING_TABLE::equals);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理缓存文件
|
|
||||||
*/
|
|
||||||
public static void clear() {
|
|
||||||
File tmpDest = new File(AppConstant.TMP_DIR_PATH);
|
|
||||||
|
|
||||||
if (tmpDest.exists())
|
|
||||||
FileUtils.deleteQuietly(tmpDest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnUnpackListener {
|
|
||||||
/**
|
|
||||||
* 开始解包
|
|
||||||
*/
|
|
||||||
void onStart();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解包完成
|
|
||||||
*
|
|
||||||
* @param unpackDirPath 解包文件夹绝对路径
|
|
||||||
*/
|
|
||||||
void onUnpackSuccess(String unpackDirPath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解包失败
|
|
||||||
*
|
|
||||||
* @param msg 失败信息
|
|
||||||
*/
|
|
||||||
void onUnpackError(String msg);
|
|
||||||
|
|
||||||
void onUnpackOver();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnPackListener {
|
|
||||||
/**
|
|
||||||
* 开始解包
|
|
||||||
*/
|
|
||||||
void onStart();
|
|
||||||
|
|
||||||
void onProgress(long current, long all);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打包完成
|
|
||||||
*/
|
|
||||||
void onPackSuccess(File packFile);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打包失败
|
|
||||||
*
|
|
||||||
* @param msg 失败信息
|
|
||||||
*/
|
|
||||||
void onPackError(String msg);
|
|
||||||
|
|
||||||
void onPackOver();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnFindTransWordListener {
|
|
||||||
void onFoundWords(List<WordItem> worlds, boolean isOver);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.file;
|
|
||||||
|
|
||||||
|
|
||||||
import javafx.scene.canvas.Canvas;
|
|
||||||
import javafx.scene.image.PixelFormat;
|
|
||||||
import javafx.scene.image.PixelWriter;
|
|
||||||
import javafx.scene.image.WritablePixelFormat;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.filechooser.FileSystemView;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.DataBufferInt;
|
|
||||||
import java.io.File;
|
|
||||||
import java.nio.IntBuffer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件图标
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class FileIcon {
|
|
||||||
//设置图标
|
|
||||||
public static Canvas getFileIconToNode(File file) {
|
|
||||||
//获取系统文件的图标
|
|
||||||
Image image = ((ImageIcon) FileSystemView.getFileSystemView().getSystemIcon(file)).getImage();
|
|
||||||
//构建图片缓冲区,设定图片缓冲区的大小和背景,背景为透明
|
|
||||||
BufferedImage bi = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.BITMASK);
|
|
||||||
//把图片画到图片缓冲区
|
|
||||||
bi.getGraphics().drawImage(image, 0, 0, null);
|
|
||||||
//将图片缓冲区的数据转换成int型数组
|
|
||||||
int[] data = ((DataBufferInt) bi.getData().getDataBuffer()).getData();
|
|
||||||
//获得写像素的格式模版
|
|
||||||
WritablePixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbInstance();
|
|
||||||
//新建javafx的画布
|
|
||||||
Canvas canvas = new Canvas(bi.getWidth() + 2, bi.getHeight() + 2);
|
|
||||||
//获取像素的写入器
|
|
||||||
PixelWriter pixelWriter = canvas.getGraphicsContext2D().getPixelWriter();
|
|
||||||
//根据写像素的格式模版把int型数组写到画布
|
|
||||||
pixelWriter.setPixels(0, 0, bi.getWidth(), bi.getHeight(), pixelFormat, data, 0, bi.getWidth());
|
|
||||||
//设置树节点的图标
|
|
||||||
return canvas;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getFileName(File file) {
|
|
||||||
return FileSystemView.getFileSystemView().getSystemDisplayName(file);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.file;
|
|
||||||
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.scene.control.TreeItem;
|
|
||||||
|
|
||||||
import javax.swing.filechooser.FileSystemView;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件目录
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class FileTreeItem extends TreeItem<String> {
|
|
||||||
public static File ROOT_FILE = FileSystemView.getFileSystemView().getRoots()[0];
|
|
||||||
|
|
||||||
//判断树节点是否被初始化,没有初始化为真
|
|
||||||
private boolean notInitialized = true;
|
|
||||||
|
|
||||||
private final File file;
|
|
||||||
private final Function<File, File[]> supplier;
|
|
||||||
|
|
||||||
public FileTreeItem(File file) {
|
|
||||||
super(FileIcon.getFileName(file), FileIcon.getFileIconToNode(file));
|
|
||||||
this.file = file;
|
|
||||||
supplier = (File f) -> {
|
|
||||||
if (((FileTreeItem) this.getParent()).getFile() == ROOT_FILE) {
|
|
||||||
String name = FileIcon.getFileName(f);
|
|
||||||
if (name.equals("网络") || name.equals("家庭组")) {
|
|
||||||
return new File[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.listFiles();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileTreeItem(File file, Function<File, File[]> supplier) {
|
|
||||||
super(FileIcon.getFileName(file), FileIcon.getFileIconToNode(file));
|
|
||||||
this.file = file;
|
|
||||||
this.supplier = supplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//重写getchildren方法,让节点被展开时加载子目录
|
|
||||||
@Override
|
|
||||||
public ObservableList<TreeItem<String>> getChildren() {
|
|
||||||
|
|
||||||
ObservableList<TreeItem<String>> children = super.getChildren();
|
|
||||||
//没有加载子目录时,则加载子目录作为树节点的孩子
|
|
||||||
if (this.notInitialized && this.isExpanded()) {
|
|
||||||
|
|
||||||
this.notInitialized = false; //设置没有初始化为假
|
|
||||||
|
|
||||||
/*
|
|
||||||
*判断树节点的文件是否是目录,
|
|
||||||
*如果是目录,着把目录里面的所有的文件添加入树节点的孩子中。
|
|
||||||
*/
|
|
||||||
if (this.getFile().isDirectory()) {
|
|
||||||
List<FileTreeItem> fileList = new ArrayList<>();
|
|
||||||
for (File f : supplier.apply(this.getFile())) {
|
|
||||||
if (f.isDirectory())
|
|
||||||
children.add(new FileTreeItem(f));
|
|
||||||
else
|
|
||||||
fileList.add(new FileTreeItem(f));
|
|
||||||
}
|
|
||||||
children.addAll(fileList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
|
||||||
//重写叶子方法,如果该文件不是目录,则返回真
|
|
||||||
@Override
|
|
||||||
public boolean isLeaf() {
|
|
||||||
|
|
||||||
return !file.isDirectory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the file
|
|
||||||
*/
|
|
||||||
public File getFile() {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,183 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.http;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLParameters;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
import java.net.Authenticator;
|
|
||||||
import java.net.CookieHandler;
|
|
||||||
import java.net.ProxySelector;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http配置参数
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class HttpConfig {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class);
|
|
||||||
/**
|
|
||||||
* http版本
|
|
||||||
*/
|
|
||||||
private HttpClient.Version version = HttpClient.Version.HTTP_2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转发策略
|
|
||||||
*/
|
|
||||||
private HttpClient.Redirect redirect = HttpClient.Redirect.NORMAL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 线程池
|
|
||||||
*/
|
|
||||||
private Executor executor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 认证
|
|
||||||
*/
|
|
||||||
private Authenticator authenticator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 代理
|
|
||||||
*/
|
|
||||||
private ProxySelector proxySelector;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CookieHandler
|
|
||||||
*/
|
|
||||||
private CookieHandler cookieHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sslContext
|
|
||||||
*/
|
|
||||||
private SSLContext sslContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sslParams
|
|
||||||
*/
|
|
||||||
private SSLParameters sslParameters;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接超时时间毫秒
|
|
||||||
*/
|
|
||||||
private int connectTimeout = 10000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 默认读取数据超时时间
|
|
||||||
*/
|
|
||||||
private int defaultReadTimeout = 1200000;
|
|
||||||
|
|
||||||
|
|
||||||
public HttpConfig() {
|
|
||||||
TrustManager[] trustAllCertificates = new X509TrustManager[]{new X509TrustManager() {
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return new X509Certificate[0]; // Not relevant.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
sslParameters = new SSLParameters();
|
|
||||||
sslParameters.setEndpointIdentificationAlgorithm("");
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
sslContext = SSLContext.getInstance("TLSv1.2");
|
|
||||||
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证
|
|
||||||
sslContext.init(null, trustAllCertificates, new SecureRandom());
|
|
||||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
|
||||||
logger.error("", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public HttpClient.Version getVersion() {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setVersion(HttpClient.Version version) {
|
|
||||||
this.version = version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getConnectTimeout() {
|
|
||||||
return connectTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConnectTimeout(int connectTimeout) {
|
|
||||||
this.connectTimeout = connectTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public HttpClient.Redirect getRedirect() {
|
|
||||||
return redirect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRedirect(HttpClient.Redirect redirect) {
|
|
||||||
this.redirect = redirect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Executor getExecutor() {
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExecutor(Executor executor) {
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Authenticator getAuthenticator() {
|
|
||||||
return authenticator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthenticator(Authenticator authenticator) {
|
|
||||||
this.authenticator = authenticator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProxySelector getProxySelector() {
|
|
||||||
return proxySelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProxySelector(ProxySelector proxySelector) {
|
|
||||||
this.proxySelector = proxySelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CookieHandler getCookieHandler() {
|
|
||||||
return cookieHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCookieHandler(CookieHandler cookieHandler) {
|
|
||||||
this.cookieHandler = cookieHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDefaultReadTimeout() {
|
|
||||||
return defaultReadTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultReadTimeout(int defaultReadTimeout) {
|
|
||||||
this.defaultReadTimeout = defaultReadTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SSLContext getSslContext() {
|
|
||||||
return sslContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SSLParameters getSslParameters() {
|
|
||||||
return sslParameters;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.thread;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 线程池管理类
|
|
||||||
*/
|
|
||||||
public final class ThreadPoolManager extends ThreadPoolExecutor {
|
|
||||||
|
|
||||||
private static volatile ThreadPoolManager sInstance;
|
|
||||||
|
|
||||||
private static ScheduledExecutorService scheduledExecutorService;
|
|
||||||
|
|
||||||
private ThreadPoolManager() {
|
|
||||||
super(32,
|
|
||||||
200,
|
|
||||||
10,
|
|
||||||
TimeUnit.SECONDS,
|
|
||||||
new LinkedBlockingQueue<>(200),
|
|
||||||
new ThreadFactory(ThreadFactory.DEFAULT_THREAD_PREFIX),
|
|
||||||
new ThreadPoolExecutor.DiscardPolicy());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ThreadPoolManager getInstance() {
|
|
||||||
if (sInstance == null) sInstance = new ThreadPoolManager();
|
|
||||||
return sInstance;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API 密钥配置
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class ApiKey {
|
|
||||||
private String appid;
|
|
||||||
private String apiKey;
|
|
||||||
|
|
||||||
public ApiKey() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiKey(String appid, String apiKey) {
|
|
||||||
this.appid = appid;
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAppid() {
|
|
||||||
return appid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getApiKey() {
|
|
||||||
return apiKey;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译引擎类型
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public enum TranslateSource {
|
|
||||||
FREE_GOOGLE("free_google", "谷歌(免费)", false, 50),
|
|
||||||
BAIDU("baidu", "百度(需认证)", true),
|
|
||||||
|
|
||||||
;
|
|
||||||
private final String name;
|
|
||||||
private final String label;
|
|
||||||
private final boolean needApiKey;
|
|
||||||
private Integer defaultQps;
|
|
||||||
|
|
||||||
TranslateSource(String name, String label, boolean needApiKey) {
|
|
||||||
// 设置接口默认qps=10
|
|
||||||
this(name, label, needApiKey, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
TranslateSource(String name, String label, boolean needApiKey, int defaultQps) {
|
|
||||||
this.name = name;
|
|
||||||
this.label = label;
|
|
||||||
this.needApiKey = needApiKey;
|
|
||||||
this.defaultQps = defaultQps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLabel() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean needApiKey() {
|
|
||||||
return needApiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getDefaultQps() {
|
|
||||||
return defaultQps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDefaultQpsStr() {
|
|
||||||
return String.valueOf(defaultQps);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TranslateSource get(String type) {
|
|
||||||
for (TranslateSource value : values()) {
|
|
||||||
if (value.getName().equals(type))
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
throw new RuntimeException("类型不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TranslateSource getByLabel(String label) {
|
|
||||||
for (TranslateSource value : values()) {
|
|
||||||
if (value.getLabel().equals(label))
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
throw new RuntimeException("类型不存在");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,227 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadFactory;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.thread.ThreadPoolManager;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.factory.TranslateFactory;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.factory.TranslateFactoryImpl;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译工具
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class TranslateUtil {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(TranslateUtil.class);
|
|
||||||
private static TranslateUtil util;
|
|
||||||
private static final DelayQueue<DelayWord> delayQueue = new DelayQueue<>();
|
|
||||||
private final TranslateFactory factory;
|
|
||||||
private static WordThread wordThread;
|
|
||||||
private static ThreadPoolExecutor threadPoolExecutor;
|
|
||||||
|
|
||||||
private TranslateUtil(TranslateFactory factory) {
|
|
||||||
this.factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TranslateUtil getInstance() {
|
|
||||||
if (util == null) {
|
|
||||||
util = new TranslateUtil(TranslateFactoryImpl.getInstance());
|
|
||||||
}
|
|
||||||
return util;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提交翻译任务
|
|
||||||
*
|
|
||||||
* @param index 序号
|
|
||||||
* @param original 原始文本
|
|
||||||
* @param listener 翻译结果回调 (主线程)
|
|
||||||
*/
|
|
||||||
public void translate(int index, String original, OnTranslateListener listener) {
|
|
||||||
|
|
||||||
// 设置延迟时间
|
|
||||||
DelayWord word = factory.getDelayWord(CustomConfig.translateSource(), index, original, listener);
|
|
||||||
// 添加到延迟队列
|
|
||||||
delayQueue.add(word);
|
|
||||||
|
|
||||||
if (wordThread == null) {
|
|
||||||
wordThread = new WordThread();
|
|
||||||
wordThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除翻译任务
|
|
||||||
*/
|
|
||||||
public void clear() {
|
|
||||||
// 尝试停止所有线程
|
|
||||||
getThreadPoolExecutor().shutdownNow();
|
|
||||||
// 清空队列
|
|
||||||
delayQueue.clear();
|
|
||||||
// 设置停止标记
|
|
||||||
if (wordThread != null)
|
|
||||||
wordThread.setStop(true);
|
|
||||||
wordThread = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取翻译任务用线程池
|
|
||||||
*/
|
|
||||||
public static ThreadPoolExecutor getThreadPoolExecutor() {
|
|
||||||
if (threadPoolExecutor == null || threadPoolExecutor.isShutdown()) {
|
|
||||||
threadPoolExecutor = new ThreadPoolExecutor(32,
|
|
||||||
200,
|
|
||||||
10,
|
|
||||||
TimeUnit.SECONDS,
|
|
||||||
new LinkedBlockingQueue<>(200),
|
|
||||||
new ThreadFactory(ThreadFactory.DEFAULT_THREAD_PREFIX),
|
|
||||||
new ThreadPoolExecutor.DiscardPolicy());
|
|
||||||
}
|
|
||||||
return threadPoolExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnTranslateListener {
|
|
||||||
void onTranslate(String result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 延迟翻译对象
|
|
||||||
*/
|
|
||||||
public static class DelayWord implements Delayed {
|
|
||||||
private TranslateSource source;
|
|
||||||
private final int index;
|
|
||||||
private final String original;
|
|
||||||
private final OnTranslateListener listener;
|
|
||||||
private long time;
|
|
||||||
|
|
||||||
public DelayWord(int index, String original, OnTranslateListener listener) {
|
|
||||||
this.index = index;
|
|
||||||
this.original = original;
|
|
||||||
this.listener = listener;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSource(TranslateSource source) {
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTime(long time, TimeUnit timeUnit) {
|
|
||||||
this.time = System.currentTimeMillis() + (time > 0 ? timeUnit.toMillis(time) : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TranslateSource getSource() {
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getIndex() {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOriginal() {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OnTranslateListener getListener() {
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDelay(TimeUnit unit) {
|
|
||||||
return time - System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(Delayed o) {
|
|
||||||
DelayWord word = (DelayWord) o;
|
|
||||||
return Integer.compare(this.index, word.index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 延迟队列处理线程
|
|
||||||
*/
|
|
||||||
private static class WordThread extends Thread {
|
|
||||||
private boolean stop = false;
|
|
||||||
|
|
||||||
public void setStop(boolean stop) {
|
|
||||||
this.stop = stop;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
List<DelayWord> tmp = new ArrayList<>();
|
|
||||||
while (!delayQueue.isEmpty()) {
|
|
||||||
// 停止处理
|
|
||||||
if (stop) {
|
|
||||||
this.interrupt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 取出待翻译文本
|
|
||||||
DelayWord take = delayQueue.take();
|
|
||||||
tmp.add(take);
|
|
||||||
|
|
||||||
if (tmp.size() < CustomConfig.translateSourceQps(take.source))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
tmp.forEach(word -> {
|
|
||||||
try {
|
|
||||||
getThreadPoolExecutor().execute(() -> {
|
|
||||||
// 翻译
|
|
||||||
try {
|
|
||||||
String translate = util.factory.translate(word.getSource(), word.getOriginal());
|
|
||||||
// 回调监听器
|
|
||||||
if (word.getListener() != null)
|
|
||||||
// 主线程处理翻译结果
|
|
||||||
Platform.runLater(() -> word.getListener().onTranslate(translate));
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("翻译出错", e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("翻译出错", e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tmp.clear();
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理剩余
|
|
||||||
tmp.forEach(word -> {
|
|
||||||
try {
|
|
||||||
ThreadPoolManager.getInstance().execute(() -> {
|
|
||||||
// 翻译
|
|
||||||
try {
|
|
||||||
String translate = util.factory.translate(word.getSource(), word.getOriginal());
|
|
||||||
// 回调监听器
|
|
||||||
if (word.getListener() != null)
|
|
||||||
// 主线程处理翻译结果
|
|
||||||
Platform.runLater(() -> word.getListener().onTranslate(translate));
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("翻译出错", e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("翻译出错", e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tmp.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate.factory;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译器接口
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public interface TranslateFactory {
|
|
||||||
/**
|
|
||||||
* 翻译处理
|
|
||||||
*
|
|
||||||
* @param source 翻译源
|
|
||||||
* @param sourceString 原始文本
|
|
||||||
* @return 翻译结果
|
|
||||||
* @throws Exception 翻译出错
|
|
||||||
*/
|
|
||||||
String translate(TranslateSource source, String sourceString) throws Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取延迟翻译对象
|
|
||||||
*
|
|
||||||
* @param source 翻译源
|
|
||||||
* @param index 序号
|
|
||||||
* @param original 原始文本
|
|
||||||
* @param listener 监听器
|
|
||||||
* @return 延迟翻译对象
|
|
||||||
*/
|
|
||||||
TranslateUtil.DelayWord getDelayWord(TranslateSource source, int index, String original, TranslateUtil.OnTranslateListener listener);
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate.factory;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.processor.AbstractTranslateProcessor;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.processor.BaiduTranslateProcessor;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.processor.FreeGoogleTranslateProcessor;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译处理器
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class TranslateFactoryImpl implements TranslateFactory {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(TranslateFactoryImpl.class);
|
|
||||||
private static TranslateFactoryImpl impl;
|
|
||||||
private final Map<String, AbstractTranslateProcessor> processorMap = new HashMap<>();
|
|
||||||
private final List<AbstractTranslateProcessor> processorList = new ArrayList<>();
|
|
||||||
|
|
||||||
private TranslateFactoryImpl() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized TranslateFactoryImpl getInstance() {
|
|
||||||
if (impl == null) {
|
|
||||||
impl = new TranslateFactoryImpl();
|
|
||||||
impl.initProcessor();
|
|
||||||
}
|
|
||||||
return impl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initProcessor() {
|
|
||||||
processorList.addAll(Arrays.asList(
|
|
||||||
new FreeGoogleTranslateProcessor(TranslateSource.FREE_GOOGLE),
|
|
||||||
new BaiduTranslateProcessor(TranslateSource.BAIDU)
|
|
||||||
));
|
|
||||||
for (AbstractTranslateProcessor processor : processorList) {
|
|
||||||
processorMap.put(processor.getSource(), processor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AbstractTranslateProcessor getProcessor(TranslateSource source) {
|
|
||||||
return processorMap.get(source.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取延迟翻译对象
|
|
||||||
*
|
|
||||||
* @param source 翻译源
|
|
||||||
* @param index 序号
|
|
||||||
* @param original 原始文本
|
|
||||||
* @param listener 监听器
|
|
||||||
* @return 延迟翻译对象
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public TranslateUtil.DelayWord getDelayWord(TranslateSource source, int index, String original, TranslateUtil.OnTranslateListener listener) {
|
|
||||||
// 生产翻译对象
|
|
||||||
TranslateUtil.DelayWord word = new TranslateUtil.DelayWord(index, original, listener);
|
|
||||||
// 设置延迟
|
|
||||||
getProcessor(source).setDelayTime(word);
|
|
||||||
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译(英->中)
|
|
||||||
* <p> TODO 切换语种
|
|
||||||
*
|
|
||||||
* @param source 翻译源
|
|
||||||
* @param sourceString 原始文本
|
|
||||||
* @return 翻译结果
|
|
||||||
* @throws Exception 翻译出错
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String translate(TranslateSource source, String sourceString) throws Exception {
|
|
||||||
return getProcessor(source).translate(sourceString);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.config.CustomConfig;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.ApiKey;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateUtil;
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.http.HttpUtil;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译处理器抽象类
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public abstract class AbstractTranslateProcessor implements TranslateProcessor {
|
|
||||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
protected static final HttpUtil httpUtil = HttpUtil.getInstance();
|
|
||||||
protected final TranslateSource translateSource;
|
|
||||||
protected ApiKey apiKey;
|
|
||||||
|
|
||||||
public AbstractTranslateProcessor(TranslateSource translateSource) {
|
|
||||||
this.translateSource = translateSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSource() {
|
|
||||||
return translateSource.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public TranslateSource source() {
|
|
||||||
return translateSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean needApiKey() {
|
|
||||||
return source().needApiKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean configuredKey() {
|
|
||||||
return CustomConfig.hasTranslateApiKey(source());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int qps() {
|
|
||||||
return CustomConfig.translateSourceQps(source());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取Api配置信息
|
|
||||||
*/
|
|
||||||
protected ApiKey getApiKey() {
|
|
||||||
if (!configuredKey()) {
|
|
||||||
String message = String.format("未配置【%s】翻译源认证信息!", source().getLabel());
|
|
||||||
logger.error(message);
|
|
||||||
throw new RuntimeException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
String appid = CustomConfig.translateSourceAppid(source());
|
|
||||||
String apikey = CustomConfig.translateSourceApikey(source());
|
|
||||||
return new ApiKey(appid, apikey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String translate(String source) throws Exception {
|
|
||||||
|
|
||||||
if (needApiKey() && !configuredKey()) {
|
|
||||||
String message = String.format("未配置【%s】翻译源认证信息!", source().getLabel());
|
|
||||||
logger.error(message);
|
|
||||||
throw new RuntimeException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return customTranslate(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 翻译处理
|
|
||||||
*
|
|
||||||
* @param source 原始文本
|
|
||||||
* @return 翻译结果
|
|
||||||
*/
|
|
||||||
public abstract String customTranslate(String source) throws Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置延迟对象
|
|
||||||
*
|
|
||||||
* @param word 带翻译单词
|
|
||||||
*/
|
|
||||||
public void setDelayTime(TranslateUtil.DelayWord word) {
|
|
||||||
// 设置翻译源
|
|
||||||
word.setSource(source());
|
|
||||||
|
|
||||||
// 设置翻译延迟
|
|
||||||
int time = word.getIndex() / qps();
|
|
||||||
word.setTime(time, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.word;
|
|
||||||
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* csv单词对象
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class WordCsvItem extends WordItem {
|
|
||||||
/**
|
|
||||||
* 简体中文
|
|
||||||
*/
|
|
||||||
private StringProperty chineseSimp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件中坐标(简体中文)
|
|
||||||
*/
|
|
||||||
private int[] positionSimp;
|
|
||||||
|
|
||||||
public WordCsvItem() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public WordCsvItem(File stringTable, int lines, String original, String chineses, int[] position, String chineseSimp, int[] positionSimp) {
|
|
||||||
super(stringTable, lines, original, chineses, position);
|
|
||||||
this.chineseSimp = new SimpleStringProperty(chineseSimp);
|
|
||||||
this.positionSimp = positionSimp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty chineseSimpProperty() {
|
|
||||||
return chineseSimp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChineseSimp() {
|
|
||||||
return chineseSimp.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChineseSimp(String chineseSimp) {
|
|
||||||
this.chineseSimp.setValue(chineseSimp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getPositionSimp() {
|
|
||||||
return positionSimp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPositionSimp(int[] positionSimp) {
|
|
||||||
this.positionSimp = positionSimp;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,98 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.word;
|
|
||||||
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 待翻译单词子项
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class WordItem {
|
|
||||||
/**
|
|
||||||
* 所在文件
|
|
||||||
* <p>PS: 文本所在的文件
|
|
||||||
*/
|
|
||||||
private File file;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 原始文本
|
|
||||||
*/
|
|
||||||
private StringProperty original;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 汉化
|
|
||||||
*/
|
|
||||||
private StringProperty chinese;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行内下标
|
|
||||||
*/
|
|
||||||
private int[] position;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件第几行
|
|
||||||
*/
|
|
||||||
private int lines;
|
|
||||||
|
|
||||||
public WordItem() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public WordItem(File file, int lines, String original, String chinese, int[] position) {
|
|
||||||
this.file = file;
|
|
||||||
this.original = new SimpleStringProperty(original);
|
|
||||||
this.chinese = new SimpleStringProperty(chinese);
|
|
||||||
this.position = position;
|
|
||||||
this.lines = lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty originalProperty() {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOriginal() {
|
|
||||||
return original.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOriginal(String original) {
|
|
||||||
this.original.setValue(original);
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty chineseProperty() {
|
|
||||||
return chinese;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChinese() {
|
|
||||||
return chinese.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChinese(String chinese) {
|
|
||||||
this.chinese.setValue(chinese);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getPosition() {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPosition(int[] position) {
|
|
||||||
this.position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLines() {
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLines(int lines) {
|
|
||||||
this.lines = lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getFile() {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFile(File file) {
|
|
||||||
this.file = file;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,228 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
|
||||||
|
|
||||||
import javafx.scene.control.*;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.layout.GridPane;
|
|
||||||
import javafx.scene.layout.Priority;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 弹窗工具
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class AlertUtil {
|
|
||||||
private static Window mOwner;
|
|
||||||
private static Builder builder;
|
|
||||||
|
|
||||||
public static void initOwner(Stage stage) {
|
|
||||||
AlertUtil.mOwner = stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder<T extends Dialog> {
|
|
||||||
T alert;
|
|
||||||
|
|
||||||
public Builder(T alert) {
|
|
||||||
this.alert = alert;
|
|
||||||
if (mOwner != null) this.alert.initOwner(mOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> title(String title) {
|
|
||||||
alert.setTitle(title);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> header(String header) {
|
|
||||||
alert.setHeaderText(header);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> content(String content) {
|
|
||||||
alert.setContentText(content);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> icon(String path) {
|
|
||||||
icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString()));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<T> icon(Image image) {
|
|
||||||
getStage().getIcons().add(image);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show() {
|
|
||||||
if (AlertUtil.builder == null) {
|
|
||||||
AlertUtil.builder = this;
|
|
||||||
} else if (AlertUtil.builder.alert.isShowing()) {
|
|
||||||
if (!Objects.equals(AlertUtil.builder.alert.getContentText(), alert.getContentText()))
|
|
||||||
((Alert) AlertUtil.builder.alert).setOnHidden(event -> {
|
|
||||||
AlertUtil.builder = null;
|
|
||||||
show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
alert.showAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AlertUtil.confirm
|
|
||||||
*/
|
|
||||||
public void show(OnClickListener listener) {
|
|
||||||
|
|
||||||
Optional<ButtonType> result = alert.showAndWait();
|
|
||||||
|
|
||||||
listener.onClicked(result.get().getText());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AlertUtil.confirm
|
|
||||||
*/
|
|
||||||
public void show(OnChoseListener listener) {
|
|
||||||
Optional<ButtonType> result = alert.showAndWait();
|
|
||||||
if (result.get() == ButtonType.OK) {
|
|
||||||
listener.confirm();
|
|
||||||
} else {
|
|
||||||
listener.cancelOrClose(result.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AlertUtil.input
|
|
||||||
* 如果用户点击了取消按钮,将会返回null
|
|
||||||
*/
|
|
||||||
public String getInput() {
|
|
||||||
Optional<String> result = alert.showAndWait();
|
|
||||||
if (result.isPresent()) {
|
|
||||||
return result.get();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AlertUtil.choices
|
|
||||||
*/
|
|
||||||
public <R> R getChoice(R... choices) {
|
|
||||||
Optional result = alert.showAndWait();
|
|
||||||
return (R) result.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Stage getStage() {
|
|
||||||
return (Stage) alert.getDialogPane().getScene().getWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder<Alert> info(String content) {
|
|
||||||
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION)).content(content).header(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder<Alert> info() {
|
|
||||||
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder<Alert> error(String message) {
|
|
||||||
return new Builder<Alert>(new Alert(Alert.AlertType.ERROR)).header(null).content(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder<Alert> warning() {
|
|
||||||
return new Builder<Alert>(new Alert(Alert.AlertType.WARNING));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder<Alert> exception(Exception ex) {
|
|
||||||
return new Builder<Alert>(exceptionAlert(ex));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Alert exceptionAlert(Exception ex) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
|
||||||
alert.setTitle("Exception Dialog");
|
|
||||||
alert.setHeaderText(ex.getClass().getSimpleName());
|
|
||||||
alert.setContentText(ex.getMessage());
|
|
||||||
|
|
||||||
// 创建可扩展的异常。
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
PrintWriter pw = new PrintWriter(sw);
|
|
||||||
ex.printStackTrace(pw);
|
|
||||||
String exceptionText = sw.toString();
|
|
||||||
|
|
||||||
Label label = new Label("The exception stacktrace was :");
|
|
||||||
|
|
||||||
TextArea textArea = new TextArea(exceptionText);
|
|
||||||
textArea.setEditable(false);
|
|
||||||
textArea.setWrapText(true);
|
|
||||||
|
|
||||||
textArea.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
textArea.setMaxHeight(Double.MAX_VALUE);
|
|
||||||
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
|
||||||
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
|
||||||
|
|
||||||
GridPane expContent = new GridPane();
|
|
||||||
expContent.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
expContent.add(label, 0, 0);
|
|
||||||
expContent.add(textArea, 0, 1);
|
|
||||||
|
|
||||||
// 将可扩展异常设置到对话框窗格中。
|
|
||||||
alert.getDialogPane().setExpandableContent(expContent);
|
|
||||||
return alert;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 确认对话框
|
|
||||||
*/
|
|
||||||
public static Builder<Alert> confirm() {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
|
||||||
alert.setTitle("确认对话框");
|
|
||||||
return new Builder<Alert>(alert);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义确认对话框 <p>
|
|
||||||
* <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
|
|
||||||
*/
|
|
||||||
public static Builder<Alert> confirm(String... buttons) {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
|
||||||
|
|
||||||
List<ButtonType> buttonList = Arrays.stream(buttons).map((type) -> {
|
|
||||||
ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER;
|
|
||||||
if ("Cancel".equals(type) || "取消".equals(type))
|
|
||||||
buttonData = ButtonBar.ButtonData.CANCEL_CLOSE;
|
|
||||||
return new ButtonType(type, buttonData);
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
|
|
||||||
alert.getButtonTypes().setAll(buttonList);
|
|
||||||
|
|
||||||
return new Builder<Alert>(alert);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder<TextInputDialog> input(String content) {
|
|
||||||
TextInputDialog dialog = new TextInputDialog();
|
|
||||||
dialog.setContentText(content);
|
|
||||||
return new Builder<TextInputDialog>(dialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public static <T> Builder<ChoiceDialog<T>> choices(String hintText, T... choices) {
|
|
||||||
ChoiceDialog<T> dialog = new ChoiceDialog<T>(choices[0], choices);
|
|
||||||
dialog.setContentText(hintText);
|
|
||||||
return new Builder<ChoiceDialog<T>>(dialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public interface OnChoseListener {
|
|
||||||
void confirm();
|
|
||||||
|
|
||||||
void cancelOrClose(ButtonType buttonType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnClickListener {
|
|
||||||
void onClicked(String result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.layout.Background;
|
|
||||||
import javafx.scene.layout.BackgroundFill;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import javafx.scene.paint.Color;
|
|
||||||
import javafx.scene.text.Font;
|
|
||||||
import javafx.stage.Modality;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javafx.stage.StageStyle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载等待弹窗
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class Loading {
|
|
||||||
protected Stage stage;
|
|
||||||
protected StackPane root;
|
|
||||||
protected Label messageLb;
|
|
||||||
protected ImageView loadingView = new ImageView(new Image("https://blog-static.cnblogs.com/files/miaoqx/loading.gif"));
|
|
||||||
|
|
||||||
public Loading(Stage owner) {
|
|
||||||
|
|
||||||
messageLb = new Label("请耐心等待...");
|
|
||||||
messageLb.setFont(Font.font(20));
|
|
||||||
|
|
||||||
root = new StackPane();
|
|
||||||
root.setMouseTransparent(true);
|
|
||||||
root.setPrefSize(owner.getWidth(), owner.getHeight());
|
|
||||||
root.setBackground(new Background(new BackgroundFill(Color.rgb(0, 0, 0, 0.3), null, null)));
|
|
||||||
root.getChildren().addAll(loadingView, messageLb);
|
|
||||||
|
|
||||||
Scene scene = new Scene(root);
|
|
||||||
scene.setFill(Color.TRANSPARENT);
|
|
||||||
|
|
||||||
stage = new Stage();
|
|
||||||
stage.setX(owner.getX());
|
|
||||||
stage.setY(owner.getY());
|
|
||||||
stage.setScene(scene);
|
|
||||||
stage.setResizable(false);
|
|
||||||
stage.initOwner(owner);
|
|
||||||
stage.initStyle(StageStyle.TRANSPARENT);
|
|
||||||
stage.initModality(Modality.APPLICATION_MODAL);
|
|
||||||
stage.getIcons().addAll(owner.getIcons());
|
|
||||||
stage.setX(owner.getX());
|
|
||||||
stage.setY(owner.getY());
|
|
||||||
stage.setHeight(owner.getHeight());
|
|
||||||
stage.setWidth(owner.getWidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更改信息
|
|
||||||
public Loading showMessage(String message) {
|
|
||||||
Platform.runLater(() -> messageLb.setText(message));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更改信息
|
|
||||||
public Loading image(Image image) {
|
|
||||||
Platform.runLater(() -> loadingView.imageProperty().set(image));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示
|
|
||||||
public void show() {
|
|
||||||
Platform.runLater(() -> stage.show());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭
|
|
||||||
public void closeStage() {
|
|
||||||
Platform.runLater(() -> stage.close());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否正在展示
|
|
||||||
public boolean showing() {
|
|
||||||
return stage.isShowing();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
|
||||||
|
|
||||||
import org.apache.commons.exec.*;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 命令工具类
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class ProcessesUtil {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ProcessesUtil.class);
|
|
||||||
private static final String NEWLINE = System.lineSeparator();
|
|
||||||
private static final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
|
|
||||||
|
|
||||||
public static boolean exec(String command) {
|
|
||||||
try {
|
|
||||||
exec(command, new OnExecuteListener() {
|
|
||||||
@Override
|
|
||||||
public void onExecute(String msg) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteSuccess(int exitValue) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteError(Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onExecuteOver() {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
handler.waitFor();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("", e);
|
|
||||||
}
|
|
||||||
return 0 == handler.getExitValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void exec(String command, OnExecuteListener listener) {
|
|
||||||
LogOutputStream logout = new LogOutputStream() {
|
|
||||||
@Override
|
|
||||||
protected void processLine(String line, int logLevel) {
|
|
||||||
if (listener != null) listener.onExecute(line + NEWLINE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CommandLine commandLine = CommandLine.parse(command);
|
|
||||||
DefaultExecutor executor = DefaultExecutor.builder().get();
|
|
||||||
executor.setStreamHandler(new PumpStreamHandler(logout, logout));
|
|
||||||
DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() {
|
|
||||||
@Override
|
|
||||||
public void onProcessComplete(int exitValue) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onExecuteSuccess(exitValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProcessFailed(ExecuteException e) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onExecuteError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
executor.execute(commandLine, handler);
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (listener != null) listener.onExecuteError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnExecuteListener {
|
|
||||||
void onExecute(String msg);
|
|
||||||
|
|
||||||
void onExecuteSuccess(int exitValue);
|
|
||||||
|
|
||||||
void onExecuteError(Exception e);
|
|
||||||
void onExecuteOver();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prevent construction.
|
|
||||||
*/
|
|
||||||
private ProcessesUtil() {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
|
||||||
|
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提示工具
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class TooltipUtil {
|
|
||||||
private static TooltipUtil util;
|
|
||||||
private final Tooltip tooltip = new Tooltip();
|
|
||||||
private Window owner;
|
|
||||||
private ChangeListener<Number> xListener;
|
|
||||||
private ChangeListener<Number> yListener;
|
|
||||||
private boolean paneMove = false;
|
|
||||||
|
|
||||||
private TooltipUtil(Window window) {
|
|
||||||
this.owner = window;
|
|
||||||
this.tooltip.styleProperty().set(
|
|
||||||
"-fx-background-color: white;" +
|
|
||||||
"-fx-text-fill: grey;" +
|
|
||||||
"-fx-font-size: 12px;"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TooltipUtil getInstance(Pane pane) {
|
|
||||||
if (pane == null) return null;
|
|
||||||
Window window = pane.getScene().getWindow();
|
|
||||||
if (window == null) return null;
|
|
||||||
|
|
||||||
if (util == null) {
|
|
||||||
util = new TooltipUtil(window);
|
|
||||||
// 窗口位置监听
|
|
||||||
util.xListener = (observable, oldValue, newValue) -> {
|
|
||||||
util.tooltip.setAnchorX(util.tooltip.getAnchorX() + (newValue.doubleValue() - oldValue.doubleValue()));
|
|
||||||
util.paneMove = true;
|
|
||||||
};
|
|
||||||
util.yListener = (observable, oldValue, newValue) -> {
|
|
||||||
util.tooltip.setAnchorY(util.tooltip.getAnchorY() + (newValue.doubleValue() - oldValue.doubleValue()));
|
|
||||||
util.paneMove = true;
|
|
||||||
};
|
|
||||||
util.tooltip.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
|
||||||
if (!newValue) util.paneMove = false;
|
|
||||||
});
|
|
||||||
// 随窗口移动
|
|
||||||
util.owner.xProperty().addListener(util.xListener);
|
|
||||||
util.owner.yProperty().addListener(util.yListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!window.equals(util.owner)) {
|
|
||||||
// 删除旧监听
|
|
||||||
util.owner.xProperty().removeListener(util.xListener);
|
|
||||||
util.owner.yProperty().removeListener(util.yListener);
|
|
||||||
// 新窗口
|
|
||||||
util.owner = window;
|
|
||||||
// 随窗口移动
|
|
||||||
util.owner.xProperty().addListener(util.xListener);
|
|
||||||
util.owner.yProperty().addListener(util.yListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击关闭
|
|
||||||
pane.setOnMouseClicked(event -> {
|
|
||||||
if (!util.paneMove) util.tooltip.hide();
|
|
||||||
util.paneMove = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
util.tooltip.hide();
|
|
||||||
|
|
||||||
return util;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showProxyTypeTip(MouseEvent event) {
|
|
||||||
tooltip.setText(
|
|
||||||
"提示:XTCP 映射成功率并不高,具体取决于 NAT 设备的复杂度。\n" +
|
|
||||||
"TCP :基础的 TCP 映射,适用于大多数服务,例如远程桌面、SSH、Minecraft、泰拉瑞亚等\n" +
|
|
||||||
"UDP :基础的 UDP 映射,适用于域名解析、部分基于 UDP 协议的游戏等\n" +
|
|
||||||
"HTTP :搭建网站专用映射,并通过 80 端口访问\n" +
|
|
||||||
"HTTPS :带有 SSL 加密的网站映射,通过 443 端口访问,服务器需要支持 SSL\n" +
|
|
||||||
"XTCP :客户端之间点对点 (P2P) 连接协议,流量不经过服务器,适合大流量传输的场景,需要两台设备之间都运行一个客户端\n" +
|
|
||||||
"STCP :安全交换 TCP 连接协议,基于 TCP,访问此服务的用户也需要运行一个客户端,才能建立连接,流量由服务器转发"
|
|
||||||
);
|
|
||||||
show(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void show(MouseEvent event) {
|
|
||||||
|
|
||||||
if (tooltip.isShowing()) {
|
|
||||||
tooltip.hide();
|
|
||||||
} else {
|
|
||||||
tooltip.show(owner);
|
|
||||||
double mx = event.getScreenX();
|
|
||||||
double my = event.getScreenY();
|
|
||||||
double tw = tooltip.widthProperty().doubleValue();
|
|
||||||
double th = tooltip.heightProperty().doubleValue();
|
|
||||||
|
|
||||||
tooltip.setX(mx - tw / 2);
|
|
||||||
tooltip.setY(my - th - 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void hide() {
|
|
||||||
tooltip.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isShowing() {
|
|
||||||
return tooltip.isShowing();
|
|
||||||
}
|
|
||||||
}
|
|
13
src/main/java/cn/octopusyan/dmt/AppLauncher.java
Normal file
13
src/main/java/cn/octopusyan/dmt/AppLauncher.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package cn.octopusyan.dmt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动类
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public class AppLauncher {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Application.launch(Application.class, args);
|
||||||
|
}
|
||||||
|
}
|
109
src/main/java/cn/octopusyan/dmt/Application.java
Normal file
109
src/main/java/cn/octopusyan/dmt/Application.java
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package cn.octopusyan.dmt;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.config.Constants;
|
||||||
|
import cn.octopusyan.dmt.common.config.Context;
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.CookieManager;
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.HttpConfig;
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.dmt.common.manager.thread.ThreadPoolManager;
|
||||||
|
import cn.octopusyan.dmt.common.util.ProcessesUtil;
|
||||||
|
import cn.octopusyan.dmt.utils.PBOUtil;
|
||||||
|
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class Application extends javafx.application.Application {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(Application.class);
|
||||||
|
@Getter
|
||||||
|
private static Stage primaryStage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() {
|
||||||
|
logger.info("application init ...");
|
||||||
|
|
||||||
|
// 初始化客户端配置
|
||||||
|
ConfigManager.load();
|
||||||
|
|
||||||
|
// 初始化 PBO工具
|
||||||
|
PBOUtil.init();
|
||||||
|
|
||||||
|
// http请求工具初始化
|
||||||
|
HttpConfig httpConfig = new HttpConfig();
|
||||||
|
httpConfig.setCookieHandler(CookieManager.get());
|
||||||
|
httpConfig.setExecutor(ThreadPoolManager.getInstance("http-pool"));
|
||||||
|
// 加载代理设置
|
||||||
|
switch (ConfigManager.proxySetup()) {
|
||||||
|
case NO_PROXY -> httpConfig.setProxySelector(HttpClient.Builder.NO_PROXY);
|
||||||
|
case SYSTEM -> httpConfig.setProxySelector(ProxySelector.getDefault());
|
||||||
|
case MANUAL -> {
|
||||||
|
if (ConfigManager.hasProxy()) {
|
||||||
|
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(
|
||||||
|
Objects.requireNonNull(ConfigManager.proxyHost()),
|
||||||
|
ConfigManager.getProxyPort()
|
||||||
|
);
|
||||||
|
httpConfig.setProxySelector(ProxySelector.of(unresolved));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpConfig.setConnectTimeout(3000);
|
||||||
|
HttpUtil.init(httpConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) throws IOException {
|
||||||
|
|
||||||
|
logger.info("application start ...");
|
||||||
|
|
||||||
|
Application.primaryStage = primaryStage;
|
||||||
|
|
||||||
|
Context.setApplication(this);
|
||||||
|
|
||||||
|
// 全局异常处理
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
|
||||||
|
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
|
||||||
|
|
||||||
|
// 主题样式
|
||||||
|
Application.setUserAgentStylesheet(ConfigManager.theme().getUserAgentStylesheet());
|
||||||
|
|
||||||
|
// 启动主界面
|
||||||
|
primaryStage.setTitle(String.format("%s %s", Constants.APP_TITLE, Constants.APP_VERSION));
|
||||||
|
Scene scene = Context.initScene();
|
||||||
|
primaryStage.setScene(scene);
|
||||||
|
primaryStage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showErrorDialog(Thread t, Throwable e) {
|
||||||
|
logger.error("未知异常", e);
|
||||||
|
Platform.runLater(() -> AlertUtil.getInstance(primaryStage).exception(new Exception(e)).show());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
logger.info("application stop ...");
|
||||||
|
// 关闭所有命令
|
||||||
|
ProcessesUtil.destroyAll();
|
||||||
|
// 保存应用数据
|
||||||
|
ConfigManager.save();
|
||||||
|
// 停止所有线程
|
||||||
|
ThreadPoolManager.shutdownAll();
|
||||||
|
// 删除缓存
|
||||||
|
FileUtils.deleteQuietly(new File(Constants.TMP_DIR_PATH));
|
||||||
|
FileUtils.deleteQuietly(new File(Constants.BAK_DIR_PATH));
|
||||||
|
// 关闭主界面
|
||||||
|
Platform.exit();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
58
src/main/java/cn/octopusyan/dmt/common/base/BaseBuilder.java
Normal file
58
src/main/java/cn/octopusyan/dmt/common/base/BaseBuilder.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package cn.octopusyan.dmt.common.base;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Dialog;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public abstract class BaseBuilder<T extends BaseBuilder<T, ?>, D extends Dialog<?>> {
|
||||||
|
protected D dialog;
|
||||||
|
|
||||||
|
public BaseBuilder(D dialog, Window mOwner) {
|
||||||
|
this.dialog = dialog;
|
||||||
|
if (mOwner != null)
|
||||||
|
this.dialog.initOwner(mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T title(String title) {
|
||||||
|
dialog.setTitle(title);
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T header(String header) {
|
||||||
|
dialog.setHeaderText(header);
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T content(String content) {
|
||||||
|
dialog.setContentText(content);
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
|
||||||
|
Node dialogPane = dialog.getDialogPane().getContent();
|
||||||
|
if (dialogPane != null && ConfigManager.theme().isDarkMode()) {
|
||||||
|
dialogPane.setStyle(STR."""
|
||||||
|
\{dialogPane.getStyle()}
|
||||||
|
-fx-border-color: rgb(209, 209, 214, 0.5);
|
||||||
|
-fx-border-width: 1;
|
||||||
|
-fx-border-radius: 10;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> dialog.showAndWait());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (dialog.isShowing()) dialog.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
144
src/main/java/cn/octopusyan/dmt/common/base/BaseController.java
Normal file
144
src/main/java/cn/octopusyan/dmt/common/base/BaseController.java
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package cn.octopusyan.dmt.common.base;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.Application;
|
||||||
|
import cn.octopusyan.dmt.common.config.Context;
|
||||||
|
import cn.octopusyan.dmt.common.util.FxmlUtil;
|
||||||
|
import cn.octopusyan.dmt.common.util.ViewUtil;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用视图控制器基类
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public abstract class BaseController<VM extends BaseViewModel> implements Initializable {
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
protected final VM viewModel;
|
||||||
|
|
||||||
|
public BaseController() {
|
||||||
|
//初始化时保存当前Controller实例
|
||||||
|
Context.getControllers().put(this.getClass().getSimpleName(), this);
|
||||||
|
|
||||||
|
// view model
|
||||||
|
VM vm = null;
|
||||||
|
Type superclass = getClass().getGenericSuperclass();
|
||||||
|
if (superclass instanceof ParameterizedType type) {
|
||||||
|
Class<VM> clazz = (Class<VM>) type.getActualTypeArguments()[0];
|
||||||
|
try {
|
||||||
|
vm = clazz.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel = vm;
|
||||||
|
viewModel.setController(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
@Override
|
||||||
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
|
// 全局窗口拖拽
|
||||||
|
if (dragWindow() && getRootPanel() != null) {
|
||||||
|
// 窗口拖拽
|
||||||
|
ViewUtil.bindDragged(getRootPanel());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
initData();
|
||||||
|
|
||||||
|
// 初始化视图样式
|
||||||
|
initViewStyle();
|
||||||
|
|
||||||
|
// 初始化视图事件
|
||||||
|
initViewAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 窗口拖拽设置
|
||||||
|
*
|
||||||
|
* @return 是否启用
|
||||||
|
*/
|
||||||
|
public boolean dragWindow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取根布局
|
||||||
|
*
|
||||||
|
* @return 根布局对象
|
||||||
|
*/
|
||||||
|
public abstract Pane getRootPanel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取根布局
|
||||||
|
* <p> 搭配 {@link FxmlUtil#load(String)} 使用
|
||||||
|
*
|
||||||
|
* @return 根布局对象
|
||||||
|
*/
|
||||||
|
protected String getRootFxml() {
|
||||||
|
System.out.println(getClass().getSimpleName());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stage getWindow() {
|
||||||
|
try {
|
||||||
|
return (Stage) getRootPanel().getScene().getWindow();
|
||||||
|
} catch (Throwable _) {
|
||||||
|
return Application.getPrimaryStage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化数据
|
||||||
|
*/
|
||||||
|
public abstract void initData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视图样式
|
||||||
|
*/
|
||||||
|
public void initViewStyle() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视图事件
|
||||||
|
*/
|
||||||
|
public abstract void initViewAction();
|
||||||
|
|
||||||
|
private static List<Field> getAllField(Class<?> class1) {
|
||||||
|
List<Field> list = new ArrayList<>();
|
||||||
|
while (class1 != Object.class) {
|
||||||
|
list.addAll(Arrays.stream(class1.getDeclaredFields()).toList());
|
||||||
|
//获取父类
|
||||||
|
class1 = class1.getSuperclass();
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exit() {
|
||||||
|
Platform.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭窗口
|
||||||
|
*/
|
||||||
|
public void onDestroy() {
|
||||||
|
Stage stage = getWindow();
|
||||||
|
stage.hide();
|
||||||
|
stage.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package cn.octopusyan.dmt.common.base;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View Model
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
public abstract class BaseViewModel<T extends BaseController> {
|
||||||
|
|
||||||
|
protected T controller;
|
||||||
|
|
||||||
|
}
|
27
src/main/java/cn/octopusyan/dmt/common/config/Constants.java
Normal file
27
src/main/java/cn/octopusyan/dmt/common/config/Constants.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package cn.octopusyan.dmt.common.config;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.util.PropertiesUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用信息
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public class Constants {
|
||||||
|
public static final String APP_TITLE = PropertiesUtils.getInstance().getProperty("app.title");
|
||||||
|
public static final String APP_NAME = PropertiesUtils.getInstance().getProperty("app.name");
|
||||||
|
public static final String APP_VERSION = PropertiesUtils.getInstance().getProperty("app.version");
|
||||||
|
|
||||||
|
public static final String DATA_DIR_PATH = Paths.get("").toFile().getAbsolutePath();
|
||||||
|
public static final String BIN_DIR_PATH = STR."\{DATA_DIR_PATH}\{File.separator}bin";
|
||||||
|
public static final String TMP_DIR_PATH = STR."\{DATA_DIR_PATH}\{File.separator}tmp";
|
||||||
|
public static final String BAK_DIR_PATH = STR."\{DATA_DIR_PATH}\{File.separator}bak";
|
||||||
|
|
||||||
|
public static final String CONFIG_FILE_PATH = STR."\{DATA_DIR_PATH}\{File.separator}config.yaml";
|
||||||
|
public static final String PBOC_FILE = STR."\{BIN_DIR_PATH}\{File.separator}pboc.exe";
|
||||||
|
public static final String CFG_CONVERT_FILE = STR."\{BIN_DIR_PATH}\{File.separator}CfgConvert.exe";
|
||||||
|
}
|
106
src/main/java/cn/octopusyan/dmt/common/config/Context.java
Normal file
106
src/main/java/cn/octopusyan/dmt/common/config/Context.java
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package cn.octopusyan.dmt.common.config;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.Application;
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseController;
|
||||||
|
import cn.octopusyan.dmt.common.util.FxmlUtil;
|
||||||
|
import cn.octopusyan.dmt.common.util.ProcessesUtil;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上下文
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class Context {
|
||||||
|
@Getter
|
||||||
|
private static Application application;
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Context.class);
|
||||||
|
public static final ObjectProperty<Scene> sceneProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制器集合
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private static final Map<String, BaseController<?>> controllers = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
private Context() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取控制工厂
|
||||||
|
public static Callback<Class<?>, Object> getControlFactory() {
|
||||||
|
return type -> {
|
||||||
|
try {
|
||||||
|
return type.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setApplication(Application application) {
|
||||||
|
Context.application = application;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化场景
|
||||||
|
*
|
||||||
|
* @return Scene
|
||||||
|
*/
|
||||||
|
public static Scene initScene() {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = FxmlUtil.load("main-view");
|
||||||
|
//底层面板
|
||||||
|
Pane root = loader.load();
|
||||||
|
Optional.ofNullable(sceneProperty.get()).ifPresentOrElse(
|
||||||
|
s -> s.setRoot(root),
|
||||||
|
() -> {
|
||||||
|
Scene scene = new Scene(root, root.getPrefWidth() + 20, root.getPrefHeight() + 20, Color.TRANSPARENT);
|
||||||
|
URL resource = Objects.requireNonNull(Context.class.getResource("/css/main-view.css"));
|
||||||
|
scene.getStylesheets().addAll(resource.toExternalForm());
|
||||||
|
scene.setFill(Color.TRANSPARENT);
|
||||||
|
sceneProperty.set(scene);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error("loadScene error", e);
|
||||||
|
}
|
||||||
|
return sceneProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openUrl(String url) {
|
||||||
|
getApplication().getHostServices().showDocument(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openFolder(File file) {
|
||||||
|
openFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openFile(File file) {
|
||||||
|
if (!file.exists()) return;
|
||||||
|
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
ProcessesUtil.init(file.getAbsolutePath()).exec("explorer.exe .");
|
||||||
|
} else {
|
||||||
|
ProcessesUtil.init(file.getParentFile().getAbsolutePath()).exec(STR."explorer.exe /select,\{file.getName()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package cn.octopusyan.dmt.common.config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称常量
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class LabelConstants {
|
||||||
|
public static final String CONFIRM = "确认";
|
||||||
|
public static final String CANCEL = "取消";
|
||||||
|
}
|
29
src/main/java/cn/octopusyan/dmt/common/enums/ProxySetup.java
Normal file
29
src/main/java/cn/octopusyan/dmt/common/enums/ProxySetup.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package cn.octopusyan.dmt.common.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理类型
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum ProxySetup {
|
||||||
|
/**
|
||||||
|
* 不使用代理
|
||||||
|
*/
|
||||||
|
NO_PROXY("no_proxy", "不使用代理"),
|
||||||
|
/**
|
||||||
|
* 系统代理
|
||||||
|
*/
|
||||||
|
SYSTEM("system", "系统代理"),
|
||||||
|
/**
|
||||||
|
* 自定义代理
|
||||||
|
*/
|
||||||
|
MANUAL("manual", "自定义代理");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String name;
|
||||||
|
}
|
@ -0,0 +1,258 @@
|
|||||||
|
package cn.octopusyan.dmt.common.manager;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.*;
|
||||||
|
import cn.octopusyan.dmt.Application;
|
||||||
|
import cn.octopusyan.dmt.common.config.Constants;
|
||||||
|
import cn.octopusyan.dmt.common.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.dmt.common.manager.thread.ThreadPoolManager;
|
||||||
|
import cn.octopusyan.dmt.model.ConfigModel;
|
||||||
|
import cn.octopusyan.dmt.model.ProxyInfo;
|
||||||
|
import cn.octopusyan.dmt.model.Translate;
|
||||||
|
import cn.octopusyan.dmt.model.UpgradeConfig;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端设置
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public class ConfigManager {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
|
||||||
|
public static ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
|
||||||
|
|
||||||
|
public static final UpgradeConfig upgradeConfig = new UpgradeConfig();
|
||||||
|
public static final String DEFAULT_THEME = new PrimerLight().getName();
|
||||||
|
public static List<Theme> THEME_LIST = List.of(
|
||||||
|
new PrimerLight(), new PrimerDark(),
|
||||||
|
new NordLight(), new NordDark(),
|
||||||
|
new CupertinoLight(), new CupertinoDark(),
|
||||||
|
new Dracula()
|
||||||
|
);
|
||||||
|
public static Map<String, Theme> THEME_MAP = THEME_LIST.stream()
|
||||||
|
.collect(Collectors.toMap(Theme::getName, Function.identity()));
|
||||||
|
|
||||||
|
private static ConfigModel configModel;
|
||||||
|
|
||||||
|
static {
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load() {
|
||||||
|
configModel = loadConfig(Constants.CONFIG_FILE_PATH, ConfigModel.class);
|
||||||
|
if (configModel == null)
|
||||||
|
configModel = new ConfigModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T loadConfig(String path, Class<T> clazz) {
|
||||||
|
File src = new File(path);
|
||||||
|
try {
|
||||||
|
if (!src.exists()) {
|
||||||
|
checkFile(src, clazz);
|
||||||
|
}
|
||||||
|
return objectMapper.readValue(src, clazz);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(String.format("load %s error", clazz.getSimpleName()), e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void checkFile(File src, Class<T> clazz) throws Exception {
|
||||||
|
if (!src.exists()) {
|
||||||
|
File parentDir = FileUtils.createParentDirectories(src);
|
||||||
|
if (!parentDir.exists())
|
||||||
|
logger.error("{} 创建失败", src.getAbsolutePath());
|
||||||
|
}
|
||||||
|
objectMapper.writeValue(src, clazz.getDeclaredConstructor().newInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save() {
|
||||||
|
try {
|
||||||
|
objectMapper.writeValue(new File(Constants.CONFIG_FILE_PATH), configModel);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("save config error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 主题 }------------------------------------------
|
||||||
|
|
||||||
|
public static String themeName() {
|
||||||
|
return configModel.getTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Theme theme() {
|
||||||
|
return THEME_MAP.get(themeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void theme(Theme theme) {
|
||||||
|
Application.setUserAgentStylesheet(theme.getUserAgentStylesheet());
|
||||||
|
configModel.setTheme(theme.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 翻译接口配置 }------------------------------------------
|
||||||
|
|
||||||
|
public static TranslateApi translateApi() {
|
||||||
|
return TranslateApi.get(configModel.getTranslate().getUse());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void translateApi(TranslateApi api) {
|
||||||
|
configModel.getTranslate().setUse(api.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Translate.Config getTranslateConfig(TranslateApi api) {
|
||||||
|
return Optional.of(configModel.getTranslate().getConfig().get(api.getName()))
|
||||||
|
.orElse(api.translate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasTranslateApiKey(TranslateApi api) {
|
||||||
|
return StringUtils.isNoneEmpty(getTranslateConfig(api).getAppId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void translateAppid(TranslateApi api, String appId) {
|
||||||
|
getTranslateConfig(api).setAppId(appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String translateAppid(TranslateApi api) {
|
||||||
|
return getTranslateConfig(api).getAppId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void translateApikey(TranslateApi api, String secretKey) {
|
||||||
|
getTranslateConfig(api).setSecretKey(secretKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String translateApikey(TranslateApi api) {
|
||||||
|
return getTranslateConfig(api).getSecretKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void translateQps(TranslateApi api, int qps) {
|
||||||
|
getTranslateConfig(api).setQps(qps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int translateQps(TranslateApi api) {
|
||||||
|
return getTranslateConfig(api).getQps();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 网络代理 }------------------------------------------
|
||||||
|
|
||||||
|
public static ProxySetup proxySetup() {
|
||||||
|
return ProxySetup.valueOf(StringUtils.upperCase(getProxyInfo().getSetup()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void proxyTestUrl(String url) {
|
||||||
|
getProxyInfo().setTestUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String proxyTestUrl() {
|
||||||
|
return getProxyInfo().getTestUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void proxySetup(ProxySetup setup) {
|
||||||
|
getProxyInfo().setSetup(setup.getCode());
|
||||||
|
|
||||||
|
switch (setup) {
|
||||||
|
case NO_PROXY -> HttpUtil.getInstance().clearProxy();
|
||||||
|
case SYSTEM, MANUAL -> {
|
||||||
|
if (ProxySetup.MANUAL.equals(setup) && !hasProxy())
|
||||||
|
return;
|
||||||
|
HttpUtil.getInstance().proxy(setup, ConfigManager.getProxyInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasProxy() {
|
||||||
|
if (configModel == null)
|
||||||
|
return false;
|
||||||
|
ProxyInfo proxyInfo = getProxyInfo();
|
||||||
|
return proxyInfo != null
|
||||||
|
&& StringUtils.isNoneEmpty(proxyInfo.getHost())
|
||||||
|
&& StringUtils.isNoneEmpty(proxyInfo.getPort())
|
||||||
|
&& Integer.parseInt(proxyInfo.getPort()) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProxyInfo getProxyInfo() {
|
||||||
|
ProxyInfo proxyInfo = configModel.getProxy();
|
||||||
|
|
||||||
|
if (proxyInfo == null)
|
||||||
|
setProxyInfo(new ProxyInfo());
|
||||||
|
|
||||||
|
return configModel.getProxy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setProxyInfo(ProxyInfo info) {
|
||||||
|
configModel.setProxy(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String proxyHost() {
|
||||||
|
return getProxyInfo().getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void proxyHost(String host) {
|
||||||
|
getProxyInfo().setHost(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String proxyPort() {
|
||||||
|
return getProxyInfo().getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getProxyPort() {
|
||||||
|
return Integer.parseInt(proxyPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void proxyPort(String port) {
|
||||||
|
if (!NumberUtils.isParsable(port)) return;
|
||||||
|
|
||||||
|
getProxyInfo().setPort(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 端口检查
|
||||||
|
*
|
||||||
|
* @param consumer 检查结果,信息
|
||||||
|
*/
|
||||||
|
public static void checkProxy(BiConsumer<Boolean, String> consumer) {
|
||||||
|
if (ProxySetup.SYSTEM.equals(proxySetup())) {
|
||||||
|
consumer.accept(true, "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!hasProxy()) return;
|
||||||
|
|
||||||
|
ThreadPoolManager.getInstance().execute(() -> {
|
||||||
|
try {
|
||||||
|
try (Socket socket = new Socket(proxyHost(), getProxyPort())) {
|
||||||
|
Platform.runLater(() -> consumer.accept(true, "success"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Platform.runLater(() -> consumer.accept(false, "connection timed out"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(STR."host=\{proxyHost()},port=\{proxyPort()}", e);
|
||||||
|
Platform.runLater(() -> consumer.accept(false, e.getMessage()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 版本检查 }------------------------------------------
|
||||||
|
|
||||||
|
public static UpgradeConfig upgradeConfig() {
|
||||||
|
return upgradeConfig;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,371 @@
|
|||||||
|
package cn.octopusyan.dmt.common.manager.http;
|
||||||
|
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cookie 管理
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class CookieManager {
|
||||||
|
|
||||||
|
private static final InMemoryCookieStore inMemoryCookieStore = new InMemoryCookieStore();
|
||||||
|
private static final java.net.CookieManager cookieManager =
|
||||||
|
new java.net.CookieManager(inMemoryCookieStore, CookiePolicy.ACCEPT_ALL);
|
||||||
|
|
||||||
|
public static java.net.CookieManager get() {
|
||||||
|
return cookieManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InMemoryCookieStore getStore() {
|
||||||
|
return inMemoryCookieStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class InMemoryCookieStore implements CookieStore {
|
||||||
|
// the in-memory representation of cookies
|
||||||
|
private final List<HttpCookie> cookieJar;
|
||||||
|
|
||||||
|
// the cookies are indexed by its domain and associated uri (if present)
|
||||||
|
// CAUTION: when a cookie removed from main data structure (i.e. cookieJar),
|
||||||
|
// it won't be cleared in domainIndex & uriIndex. Double-check the
|
||||||
|
// presence of cookie when retrieve one form index store.
|
||||||
|
private final Map<String, List<HttpCookie>> domainIndex;
|
||||||
|
private final Map<URI, List<HttpCookie>> uriIndex;
|
||||||
|
|
||||||
|
// use ReentrantLock instead of synchronized for scalability
|
||||||
|
private final ReentrantLock lock;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default ctor
|
||||||
|
*/
|
||||||
|
public InMemoryCookieStore() {
|
||||||
|
cookieJar = new ArrayList<>();
|
||||||
|
domainIndex = new HashMap<>();
|
||||||
|
uriIndex = new HashMap<>();
|
||||||
|
|
||||||
|
lock = new ReentrantLock(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add one cookie into cookie store.
|
||||||
|
*/
|
||||||
|
public void add(URI uri, HttpCookie cookie) {
|
||||||
|
// pre-condition : argument can't be null
|
||||||
|
if (cookie == null) {
|
||||||
|
throw new NullPointerException("cookie is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// remove the ole cookie if there has had one
|
||||||
|
cookieJar.remove(cookie);
|
||||||
|
|
||||||
|
// add new cookie if it has a non-zero max-age
|
||||||
|
if (cookie.getMaxAge() != 0) {
|
||||||
|
cookieJar.add(cookie);
|
||||||
|
// and add it to domain index
|
||||||
|
if (cookie.getDomain() != null) {
|
||||||
|
addIndex(domainIndex, cookie.getDomain(), cookie);
|
||||||
|
}
|
||||||
|
if (uri != null) {
|
||||||
|
// add it to uri index, too
|
||||||
|
addIndex(uriIndex, getEffectiveURI(uri), cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all cookies, which:
|
||||||
|
* 1) given uri domain-matches with, or, associated with
|
||||||
|
* given uri when added to the cookie store.
|
||||||
|
* 3) not expired.
|
||||||
|
* See RFC 2965 sec. 3.3.4 for more detail.
|
||||||
|
*/
|
||||||
|
public List<HttpCookie> get(URI uri) {
|
||||||
|
// argument can't be null
|
||||||
|
if (uri == null) {
|
||||||
|
throw new NullPointerException("uri is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HttpCookie> cookies = new ArrayList<>();
|
||||||
|
boolean secureLink = "https".equalsIgnoreCase(uri.getScheme());
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
// check domainIndex first
|
||||||
|
getInternal1(cookies, domainIndex, uri.getHost(), secureLink);
|
||||||
|
// check uriIndex then
|
||||||
|
getInternal2(cookies, uriIndex, getEffectiveURI(uri), secureLink);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all cookies in cookie store, except those have expired
|
||||||
|
*/
|
||||||
|
public List<HttpCookie> getCookies() {
|
||||||
|
List<HttpCookie> rt;
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
cookieJar.removeIf(HttpCookie::hasExpired);
|
||||||
|
} finally {
|
||||||
|
rt = Collections.unmodifiableList(cookieJar);
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all URIs, which are associated with at least one cookie
|
||||||
|
* of this cookie store.
|
||||||
|
*/
|
||||||
|
public List<URI> getURIs() {
|
||||||
|
List<URI> uris;
|
||||||
|
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
Iterator<URI> it = uriIndex.keySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
URI uri = it.next();
|
||||||
|
List<HttpCookie> cookies = uriIndex.get(uri);
|
||||||
|
if (cookies == null || cookies.isEmpty()) {
|
||||||
|
// no cookies list or an empty list associated with
|
||||||
|
// this uri entry, delete it
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
uris = new ArrayList<>(uriIndex.keySet());
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a cookie from store
|
||||||
|
*/
|
||||||
|
public boolean remove(URI uri, HttpCookie ck) {
|
||||||
|
// argument can't be null
|
||||||
|
if (ck == null) {
|
||||||
|
throw new NullPointerException("cookie is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean modified;
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
modified = cookieJar.remove(ck);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all cookies in this cookie store.
|
||||||
|
*/
|
||||||
|
public boolean removeAll() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (cookieJar.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cookieJar.clear();
|
||||||
|
domainIndex.clear();
|
||||||
|
uriIndex.clear();
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------------- Private operations -------------- */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is almost the same as HttpCookie.domainMatches except for
|
||||||
|
* one difference: It won't reject cookies when the 'H' part of the
|
||||||
|
* domain contains a dot ('.').
|
||||||
|
* I.E.: RFC 2965 section 3.3.2 says that if host is x.y.domain.com
|
||||||
|
* and the cookie domain is .domain.com, then it should be rejected.
|
||||||
|
* However that's not how the real world works. Browsers don't reject and
|
||||||
|
* some sites, like yahoo.com do actually expect these cookies to be
|
||||||
|
* passed along.
|
||||||
|
* And should be used for 'old' style cookies (aka Netscape type of cookies)
|
||||||
|
*/
|
||||||
|
private boolean netscapeDomainMatches(String domain, String host) {
|
||||||
|
if (domain == null || host == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's no embedded dot in domain and domain is not .local
|
||||||
|
boolean isLocalDomain = ".local".equalsIgnoreCase(domain);
|
||||||
|
int embeddedDotInDomain = domain.indexOf('.');
|
||||||
|
if (embeddedDotInDomain == 0) {
|
||||||
|
embeddedDotInDomain = domain.indexOf('.', 1);
|
||||||
|
}
|
||||||
|
if (!isLocalDomain && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the host name contains no dot and the domain name is .local
|
||||||
|
int firstDotInHost = host.indexOf('.');
|
||||||
|
if (firstDotInHost == -1 && isLocalDomain) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int domainLength = domain.length();
|
||||||
|
int lengthDiff = host.length() - domainLength;
|
||||||
|
if (lengthDiff == 0) {
|
||||||
|
// if the host name and the domain name are just string-compare equal
|
||||||
|
return host.equalsIgnoreCase(domain);
|
||||||
|
} else if (lengthDiff > 0) {
|
||||||
|
// need to check H & D component
|
||||||
|
String H = host.substring(0, lengthDiff);
|
||||||
|
String D = host.substring(lengthDiff);
|
||||||
|
|
||||||
|
return (D.equalsIgnoreCase(domain));
|
||||||
|
} else if (lengthDiff == -1) {
|
||||||
|
// if domain is actually .host
|
||||||
|
return (domain.charAt(0) == '.' &&
|
||||||
|
host.equalsIgnoreCase(domain.substring(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getInternal1(List<HttpCookie> cookies, Map<String, List<HttpCookie>> cookieIndex,
|
||||||
|
String host, boolean secureLink) {
|
||||||
|
// Use a separate list to handle cookies that need to be removed so
|
||||||
|
// that there is no conflict with iterators.
|
||||||
|
ArrayList<HttpCookie> toRemove = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, List<HttpCookie>> entry : cookieIndex.entrySet()) {
|
||||||
|
String domain = entry.getKey();
|
||||||
|
List<HttpCookie> lst = entry.getValue();
|
||||||
|
for (HttpCookie c : lst) {
|
||||||
|
if ((c.getVersion() == 0 && netscapeDomainMatches(domain, host)) ||
|
||||||
|
(c.getVersion() == 1 && HttpCookie.domainMatches(domain, host))) {
|
||||||
|
if ((cookieJar.contains(c))) {
|
||||||
|
// the cookie still in main cookie store
|
||||||
|
if (!c.hasExpired()) {
|
||||||
|
// don't add twice and make sure it's the proper
|
||||||
|
// security level
|
||||||
|
if ((secureLink || !c.getSecure()) &&
|
||||||
|
!cookies.contains(c)) {
|
||||||
|
cookies.add(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toRemove.add(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the cookie has been removed from main store,
|
||||||
|
// so also remove it from domain indexed store
|
||||||
|
toRemove.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear up the cookies that need to be removed
|
||||||
|
for (HttpCookie c : toRemove) {
|
||||||
|
lst.remove(c);
|
||||||
|
cookieJar.remove(c);
|
||||||
|
|
||||||
|
}
|
||||||
|
toRemove.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @param cookies [OUT] contains the found cookies
|
||||||
|
// @param cookieIndex the index
|
||||||
|
// @param comparator the prediction to decide whether or not
|
||||||
|
// a cookie in index should be returned
|
||||||
|
private <T> void getInternal2(List<HttpCookie> cookies,
|
||||||
|
Map<T, List<HttpCookie>> cookieIndex,
|
||||||
|
Comparable<T> comparator, boolean secureLink) {
|
||||||
|
for (T index : cookieIndex.keySet()) {
|
||||||
|
if (comparator.compareTo(index) == 0) {
|
||||||
|
List<HttpCookie> indexedCookies = cookieIndex.get(index);
|
||||||
|
// check the list of cookies associated with this domain
|
||||||
|
if (indexedCookies != null) {
|
||||||
|
Iterator<HttpCookie> it = indexedCookies.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
HttpCookie ck = it.next();
|
||||||
|
if (cookieJar.contains(ck)) {
|
||||||
|
// the cookie still in main cookie store
|
||||||
|
if (!ck.hasExpired()) {
|
||||||
|
// don't add twice
|
||||||
|
if ((secureLink || !ck.getSecure()) &&
|
||||||
|
!cookies.contains(ck))
|
||||||
|
cookies.add(ck);
|
||||||
|
} else {
|
||||||
|
it.remove();
|
||||||
|
cookieJar.remove(ck);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the cookie has been removed from main store,
|
||||||
|
// so also remove it from domain indexed store
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end of indexedCookies != null
|
||||||
|
} // end of comparator.compareTo(index) == 0
|
||||||
|
} // end of cookieIndex iteration
|
||||||
|
}
|
||||||
|
|
||||||
|
// add 'cookie' indexed by 'index' into 'indexStore'
|
||||||
|
private <T> void addIndex(Map<T, List<HttpCookie>> indexStore,
|
||||||
|
T index,
|
||||||
|
HttpCookie cookie) {
|
||||||
|
if (index != null) {
|
||||||
|
List<HttpCookie> cookies = indexStore.get(index);
|
||||||
|
if (cookies != null) {
|
||||||
|
// there may already have the same cookie, so remove it first
|
||||||
|
cookies.remove(cookie);
|
||||||
|
|
||||||
|
cookies.add(cookie);
|
||||||
|
} else {
|
||||||
|
cookies = new ArrayList<>();
|
||||||
|
cookies.add(cookie);
|
||||||
|
indexStore.put(index, cookies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// for cookie purpose, the effective uri should only be http://host
|
||||||
|
// the path will be taken into account when path-match algorithm applied
|
||||||
|
//
|
||||||
|
private URI getEffectiveURI(URI uri) {
|
||||||
|
URI effectiveURI;
|
||||||
|
try {
|
||||||
|
effectiveURI = new URI("http",
|
||||||
|
uri.getHost(),
|
||||||
|
null, // path component
|
||||||
|
null, // query component
|
||||||
|
null // fragment component
|
||||||
|
);
|
||||||
|
} catch (URISyntaxException ignored) {
|
||||||
|
effectiveURI = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return effectiveURI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
package cn.octopusyan.dmt.common.manager.http;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLParameters;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.net.Authenticator;
|
||||||
|
import java.net.CookieHandler;
|
||||||
|
import java.net.ProxySelector;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http配置参数
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class HttpConfig {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 使用系统默认代理
|
||||||
|
System.setProperty("java.net.useSystemProxies", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http版本
|
||||||
|
*/
|
||||||
|
private HttpClient.Version version = HttpClient.Version.HTTP_2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转发策略
|
||||||
|
*/
|
||||||
|
private HttpClient.Redirect redirect = HttpClient.Redirect.NORMAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程池
|
||||||
|
*/
|
||||||
|
private Executor executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证
|
||||||
|
*/
|
||||||
|
private Authenticator authenticator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理
|
||||||
|
*/
|
||||||
|
private ProxySelector proxySelector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CookieHandler
|
||||||
|
*/
|
||||||
|
private CookieHandler cookieHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sslContext
|
||||||
|
*/
|
||||||
|
private SSLContext sslContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sslParams
|
||||||
|
*/
|
||||||
|
private SSLParameters sslParameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接超时时间毫秒
|
||||||
|
*/
|
||||||
|
private int connectTimeout = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认读取数据超时时间
|
||||||
|
*/
|
||||||
|
private int defaultReadTimeout = 1200000;
|
||||||
|
|
||||||
|
|
||||||
|
public HttpConfig() {
|
||||||
|
// SSL
|
||||||
|
sslParameters = new SSLParameters();
|
||||||
|
sslParameters.setEndpointIdentificationAlgorithm("");
|
||||||
|
sslParameters.setProtocols(new String[]{"TLSv1.2"});
|
||||||
|
try {
|
||||||
|
sslContext = SSLContext.getInstance("TLSv1.2");
|
||||||
|
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证
|
||||||
|
sslContext.init(null, trustAllCertificates, new SecureRandom());
|
||||||
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
|
logger.error("", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final TrustManager[] trustAllCertificates = new X509TrustManager[]{new X509TrustManager() {
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[0]; // Not relevant.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
@ -1,7 +1,13 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.http;
|
package cn.octopusyan.dmt.common.manager.http;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import cn.octopusyan.dmt.common.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.response.BodyHandler;
|
||||||
|
import cn.octopusyan.dmt.common.util.JsonUtil;
|
||||||
|
import cn.octopusyan.dmt.model.ProxyInfo;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
@ -11,15 +17,19 @@ import java.net.http.HttpClient;
|
|||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 网络请求封装
|
* 网络请求封装
|
||||||
*
|
*
|
||||||
* @author octopus_yan@foxmail.com
|
* @author octopus_yan@foxmail.com
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class HttpUtil {
|
public class HttpUtil {
|
||||||
private volatile static HttpUtil util;
|
private volatile static HttpUtil util;
|
||||||
private volatile HttpClient httpClient;
|
private volatile HttpClient httpClient;
|
||||||
@ -57,15 +67,20 @@ public class HttpUtil {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpUtil proxy(String host, int port) {
|
public void proxy(ProxySetup setup, ProxyInfo proxy) {
|
||||||
if (httpClient == null)
|
if (httpClient == null)
|
||||||
throw new RuntimeException("are you ready ?");
|
throw new RuntimeException("are you ready ?");
|
||||||
|
|
||||||
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(host, port);
|
switch (setup) {
|
||||||
ProxySelector other = ProxySelector.of(unresolved);
|
case NO_PROXY -> clearProxy();
|
||||||
this.httpConfig.setProxySelector(other);
|
case SYSTEM -> httpConfig.setProxySelector(ProxySelector.getDefault());
|
||||||
|
case MANUAL -> {
|
||||||
|
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(proxy.getHost(), Integer.parseInt(proxy.getPort()));
|
||||||
|
httpConfig.setProxySelector(ProxySelector.of(unresolved));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.httpClient = createClient(httpConfig);
|
this.httpClient = createClient(httpConfig);
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearProxy() {
|
public void clearProxy() {
|
||||||
@ -76,24 +91,26 @@ public class HttpUtil {
|
|||||||
httpClient = createClient(httpConfig);
|
httpClient = createClient(httpConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClient getHttpClient() {
|
public void close() {
|
||||||
return httpClient;
|
if (httpClient == null) return;
|
||||||
|
httpClient.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String get(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
|
public String get(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
|
||||||
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header).GET();
|
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header).GET();
|
||||||
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
return response.body();
|
return response.body();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String post(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
|
public String post(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
|
||||||
HttpRequest.Builder request = getRequest(uri, header)
|
HttpRequest.Builder request = getRequest(uri, header)
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(param.toJSONString()));
|
.header("Content-Type", "application/json;charset=utf-8")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(JsonUtil.toJsonString(param)));
|
||||||
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||||
return response.body();
|
return response.body();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String postForm(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
|
public String postForm(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
|
||||||
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header)
|
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header)
|
||||||
.POST(HttpRequest.BodyPublishers.noBody());
|
.POST(HttpRequest.BodyPublishers.noBody());
|
||||||
|
|
||||||
@ -101,39 +118,73 @@ public class HttpUtil {
|
|||||||
return response.body();
|
return response.body();
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpRequest.Builder getRequest(String uri, JSONObject header) {
|
public void download(String url, String savePath, BiConsumer<Long, Long> listener) throws IOException, InterruptedException {
|
||||||
|
HttpRequest request = getRequest(url, null).build();
|
||||||
|
// 检查bin目录
|
||||||
|
File binDir = new File(savePath);
|
||||||
|
if (!binDir.exists()) {
|
||||||
|
log.debug(STR."dir [\{savePath}] not exists");
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
binDir.mkdirs();
|
||||||
|
log.debug(STR."created dir [\{savePath}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载处理器
|
||||||
|
var handler = BodyHandler.create(
|
||||||
|
Path.of(savePath),
|
||||||
|
StandardOpenOption.CREATE, StandardOpenOption.WRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
// 下载监听
|
||||||
|
if (listener != null)
|
||||||
|
handler.listener(listener);
|
||||||
|
|
||||||
|
HttpResponse<Path> response = httpClient.send(request, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequest.Builder getRequest(String uri, JsonNode header) {
|
||||||
HttpRequest.Builder request = HttpRequest.newBuilder();
|
HttpRequest.Builder request = HttpRequest.newBuilder();
|
||||||
// 请求地址
|
// 请求地址
|
||||||
request.uri(URI.create(uri));
|
request.uri(URI.create(uri));
|
||||||
// 请求头
|
// 请求头
|
||||||
if (header != null && !header.isEmpty()) {
|
if (header != null && !header.isEmpty()) {
|
||||||
for (String key : header.keySet()) {
|
for (Map.Entry<String, JsonNode> property : header.properties()) {
|
||||||
request.header(key, header.getString(key));
|
String key = property.getKey();
|
||||||
|
request.header(key, JsonUtil.toJsonString(property.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Cookie
|
||||||
|
// List<HttpCookie> cookies = CookieManager.getStore().get(URI.create(uri));
|
||||||
|
// if (!cookies.isEmpty()) {
|
||||||
|
// String cookie = cookies.stream()
|
||||||
|
// .map(item -> STR."\{item.getName()}=\{item.getValue()}")
|
||||||
|
// .collect(Collectors.joining(";"));
|
||||||
|
// request.header("Cookie", cookie);
|
||||||
|
// }
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createFormParams(JSONObject params) {
|
private String createFormParams(JsonNode params) {
|
||||||
StringBuilder formParams = new StringBuilder();
|
StringBuilder formParams = new StringBuilder();
|
||||||
if (params == null) {
|
if (params == null) {
|
||||||
return formParams.toString();
|
return formParams.toString();
|
||||||
}
|
}
|
||||||
for (String key : params.keySet()) {
|
for (Map.Entry<String, JsonNode> property : params.properties()) {
|
||||||
Object value = params.get(key);
|
String key = property.getKey();
|
||||||
if (value instanceof String) {
|
JsonNode value = params.get(key);
|
||||||
value = URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8);
|
if (value.isTextual()) {
|
||||||
|
String value_ = URLEncoder.encode(String.valueOf(value.asText()), StandardCharsets.UTF_8);
|
||||||
|
formParams.append("&").append(key).append("=").append(value_);
|
||||||
|
} else if (value.isNumber()) {
|
||||||
formParams.append("&").append(key).append("=").append(value);
|
formParams.append("&").append(key).append("=").append(value);
|
||||||
} else if (value instanceof Number) {
|
} else if (value.isArray()) {
|
||||||
formParams.append("&").append(key).append("=").append(value);
|
formParams.append("&").append(key).append("=").append(JsonUtil.toJsonString(value));
|
||||||
} else if (value instanceof List) {
|
|
||||||
formParams.append("&").append(key).append("=").append(params.getJSONArray(key));
|
|
||||||
} else {
|
} else {
|
||||||
formParams.append("&").append(key).append("=").append(params.getJSONObject(key));
|
formParams.append("&").append(key).append("=").append(JsonUtil.toJsonString(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!formParams.isEmpty()) {
|
if (!formParams.isEmpty()) {
|
||||||
formParams = new StringBuilder("?" + formParams.substring(1));
|
formParams = new StringBuilder(STR."?\{formParams.substring(1)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return formParams.toString();
|
return formParams.toString();
|
@ -0,0 +1,102 @@
|
|||||||
|
package cn.octopusyan.dmt.common.manager.http.response;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.OpenOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.concurrent.Flow;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载处理
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class BodyHandler implements HttpResponse.BodyHandler<Path> {
|
||||||
|
private final HttpResponse.BodyHandler<Path> handler;
|
||||||
|
private BiConsumer<Long, Long> consumer;
|
||||||
|
|
||||||
|
private BodyHandler(HttpResponse.BodyHandler<Path> handler) {
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BodyHandler create(Path directory, OpenOption... openOptions) {
|
||||||
|
return new BodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpResponse.BodySubscriber<Path> apply(HttpResponse.ResponseInfo responseInfo) {
|
||||||
|
AtomicLong length = new AtomicLong(-1);
|
||||||
|
// 获取文件大小
|
||||||
|
Optional<String> string = responseInfo.headers().firstValue("content-length");
|
||||||
|
string.ifPresentOrElse(s -> {
|
||||||
|
length.set(Long.parseLong(s));
|
||||||
|
log.debug(STR."========={content-length = \{s}}=========");
|
||||||
|
}, () -> {
|
||||||
|
String msg = "response not has header [content-length]";
|
||||||
|
log.error(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
BodySubscriber subscriber = new BodySubscriber(handler.apply(responseInfo));
|
||||||
|
subscriber.setConsumer(progress -> consumer.accept(length.get(), progress));
|
||||||
|
|
||||||
|
return subscriber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void listener(BiConsumer<Long, Long> consumer) {
|
||||||
|
this.consumer = consumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BodySubscriber implements HttpResponse.BodySubscriber<Path> {
|
||||||
|
private final HttpResponse.BodySubscriber<Path> subscriber;
|
||||||
|
private final AtomicLong progress = new AtomicLong(0);
|
||||||
|
@Setter
|
||||||
|
private Consumer<Long> consumer;
|
||||||
|
|
||||||
|
public BodySubscriber(HttpResponse.BodySubscriber<Path> subscriber) {
|
||||||
|
this.subscriber = subscriber;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<Path> getBody() {
|
||||||
|
return subscriber.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Flow.Subscription subscription) {
|
||||||
|
subscriber.onSubscribe(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(List<ByteBuffer> item) {
|
||||||
|
subscriber.onNext(item);
|
||||||
|
|
||||||
|
// 记录进度
|
||||||
|
for (ByteBuffer byteBuffer : item) {
|
||||||
|
progress.addAndGet(byteBuffer.limit());
|
||||||
|
}
|
||||||
|
consumer.accept(progress.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable throwable) {
|
||||||
|
subscriber.onError(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
subscriber.onComplete();
|
||||||
|
|
||||||
|
consumer.accept(progress.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.thread;
|
package cn.octopusyan.dmt.common.manager.thread;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -26,7 +26,7 @@ public class ThreadFactory implements java.util.concurrent.ThreadFactory {
|
|||||||
|
|
||||||
public ThreadFactory(String prefix) {
|
public ThreadFactory(String prefix) {
|
||||||
group = Thread.currentThread().getThreadGroup();
|
group = Thread.currentThread().getThreadGroup();
|
||||||
namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-";
|
namePrefix = STR."\{prefix}-\{poolNumber.getAndIncrement()}-thread-";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -36,9 +36,7 @@ public class ThreadFactory implements java.util.concurrent.ThreadFactory {
|
|||||||
namePrefix + threadNumber.getAndIncrement(),
|
namePrefix + threadNumber.getAndIncrement(),
|
||||||
0);
|
0);
|
||||||
|
|
||||||
t.setUncaughtExceptionHandler((t1, e) -> {
|
t.setUncaughtExceptionHandler((t1, e) -> logger.error("thread : {}, error", t1.getName(), e));
|
||||||
logger.error("thread : {}, error", t1.getName(), e);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (t.isDaemon())
|
if (t.isDaemon())
|
||||||
t.setDaemon(false);
|
t.setDaemon(false);
|
@ -0,0 +1,55 @@
|
|||||||
|
package cn.octopusyan.dmt.common.manager.thread;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 线程池管理类
|
||||||
|
*/
|
||||||
|
public final class ThreadPoolManager extends ThreadPoolExecutor {
|
||||||
|
|
||||||
|
private static volatile ThreadPoolManager sInstance;
|
||||||
|
private static final List<ThreadPoolManager> poolManagerList = new ArrayList<>();
|
||||||
|
|
||||||
|
private ThreadPoolManager() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadPoolManager(String threadPoolName) {
|
||||||
|
super(32,
|
||||||
|
200,
|
||||||
|
10,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingQueue<>(200),
|
||||||
|
new ThreadFactory(StringUtils.isEmpty(threadPoolName) ? ThreadFactory.DEFAULT_THREAD_PREFIX : threadPoolName),
|
||||||
|
new DiscardPolicy());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ThreadPoolManager getInstance(String threadPoolName) {
|
||||||
|
ThreadPoolManager threadPoolManager = new ThreadPoolManager(threadPoolName);
|
||||||
|
poolManagerList.add(threadPoolManager);
|
||||||
|
return threadPoolManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ThreadPoolManager getInstance() {
|
||||||
|
if (sInstance == null) {
|
||||||
|
synchronized (ThreadPoolManager.class) {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = new ThreadPoolManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void shutdownAll() {
|
||||||
|
getInstance().shutdown();
|
||||||
|
poolManagerList.forEach(ThreadPoolExecutor::shutdown);
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,21 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
package cn.octopusyan.dmt.common.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.*;
|
import java.awt.datatransfer.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p> author : octopus yan
|
* 剪切板工具
|
||||||
* <p> email : octopus_yan@foxmail.com
|
*
|
||||||
* <p> description : 剪切板工具
|
* @author octopus_yan
|
||||||
* <p> create : 2022-4-14 23:21
|
|
||||||
*/
|
*/
|
||||||
public class ClipUtil {
|
public class ClipUtil {
|
||||||
//获取系统剪切板
|
//获取系统剪切板
|
||||||
private static final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
private static final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ClipUtil.class);
|
||||||
|
|
||||||
public static void setClip(String data) {
|
public static void setClip(String data) {
|
||||||
//构建String数据类型
|
//构建String数据类型
|
||||||
@ -28,7 +31,7 @@ public class ClipUtil {
|
|||||||
//从数据中获取文本值
|
//从数据中获取文本值
|
||||||
return (String) content.getTransferData(DataFlavor.stringFlavor);
|
return (String) content.getTransferData(DataFlavor.stringFlavor);
|
||||||
} catch (UnsupportedFlavorException | IOException e) {
|
} catch (UnsupportedFlavorException | IOException e) {
|
||||||
e.printStackTrace();
|
log.error("", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
package cn.octopusyan.dmt.common.util;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.config.Context;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.JavaFXBuilderFactory;
|
import javafx.fxml.JavaFXBuilderFactory;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ public class FxmlUtil {
|
|||||||
FxmlUtil.class.getResource(prefix + name + suffix),
|
FxmlUtil.class.getResource(prefix + name + suffix),
|
||||||
null,
|
null,
|
||||||
new JavaFXBuilderFactory(),
|
new JavaFXBuilderFactory(),
|
||||||
null,
|
Context.getControlFactory(),
|
||||||
StandardCharsets.UTF_8
|
StandardCharsets.UTF_8
|
||||||
);
|
);
|
||||||
}
|
}
|
187
src/main/java/cn/octopusyan/dmt/common/util/JsonUtil.java
Normal file
187
src/main/java/cn/octopusyan/dmt/common/util/JsonUtil.java
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package cn.octopusyan.dmt.common.util;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jackson 封装工具类
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class JsonUtil {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
|
||||||
|
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间日期格式
|
||||||
|
*/
|
||||||
|
private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
|
||||||
|
static {
|
||||||
|
//对象的所有字段全部列入序列化
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
|
||||||
|
//取消默认转换timestamps形式
|
||||||
|
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
|
//忽略空Bean转json的错误
|
||||||
|
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||||
|
//所有的日期格式都统一为以下的格式,即yyyy-MM-dd HH:mm:ss
|
||||||
|
objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT));
|
||||||
|
//忽略 在json字符串中存在,但在java对象中不存在对应属性的情况。防止错误
|
||||||
|
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Json字符串 转 JavaBean
|
||||||
|
*
|
||||||
|
* @param jsonString Json字符串
|
||||||
|
* @param clazz Java类对象
|
||||||
|
* @param <T> Java类
|
||||||
|
* @return JavaBean
|
||||||
|
*/
|
||||||
|
public static <T> T parseObject(String jsonString, Class<T> clazz) {
|
||||||
|
T t = null;
|
||||||
|
try {
|
||||||
|
t = objectMapper.readValue(jsonString, clazz);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取Json文件 转 JavaBean
|
||||||
|
*
|
||||||
|
* @param file Json文件
|
||||||
|
* @param clazz Java类对象
|
||||||
|
* @param <T> Java类
|
||||||
|
* @return JavaBean
|
||||||
|
*/
|
||||||
|
public static <T> T parseObject(File file, Class<T> clazz) {
|
||||||
|
T t = null;
|
||||||
|
try {
|
||||||
|
t = objectMapper.readValue(file, clazz);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取Json字符串 转 JavaBean集合
|
||||||
|
*
|
||||||
|
* @param jsonArray Json字符串
|
||||||
|
* @param reference 类型
|
||||||
|
* @param <T> JavaBean类型
|
||||||
|
* @return JavaBean集合
|
||||||
|
*/
|
||||||
|
public static <T> T parseJsonArray(String jsonArray, TypeReference<T> reference) {
|
||||||
|
T t = null;
|
||||||
|
try {
|
||||||
|
t = objectMapper.readValue(jsonArray, reference);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaBean 转 Json字符串
|
||||||
|
*
|
||||||
|
* @param object JavaBean
|
||||||
|
* @return Json字符串
|
||||||
|
*/
|
||||||
|
public static String toJsonString(Object object) {
|
||||||
|
String jsonString = null;
|
||||||
|
try {
|
||||||
|
jsonString = objectMapper.writeValueAsString(object);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return jsonString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaBean 转 字节数组
|
||||||
|
*
|
||||||
|
* @param object JavaBean
|
||||||
|
* @return 字节数组
|
||||||
|
*/
|
||||||
|
public static byte[] toByteArray(Object object) {
|
||||||
|
byte[] bytes = null;
|
||||||
|
try {
|
||||||
|
bytes = objectMapper.writeValueAsBytes(object);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaBean序列化到文件
|
||||||
|
*
|
||||||
|
* @param file 写入文件对象
|
||||||
|
* @param object JavaBean
|
||||||
|
*/
|
||||||
|
public static void objectToFile(File file, Object object) {
|
||||||
|
try {
|
||||||
|
objectMapper.writeValue(file, object);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Json字符串 转 JsonNode
|
||||||
|
*
|
||||||
|
* @param jsonString Json字符串
|
||||||
|
* @return JsonNode
|
||||||
|
*/
|
||||||
|
public static JsonNode parseJsonObject(String jsonString) {
|
||||||
|
JsonNode jsonNode = null;
|
||||||
|
try {
|
||||||
|
jsonNode = objectMapper.readTree(jsonString);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return jsonNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaBean 转 JsonNode
|
||||||
|
*
|
||||||
|
* @param object JavaBean
|
||||||
|
* @return JsonNode
|
||||||
|
*/
|
||||||
|
public static JsonNode parseJsonObject(Object object) {
|
||||||
|
return objectMapper.valueToTree(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JsonNode 转 Json字符串
|
||||||
|
*
|
||||||
|
* @param jsonNode JsonNode
|
||||||
|
* @return Json字符串
|
||||||
|
*/
|
||||||
|
public static String toJsonString(JsonNode jsonNode) {
|
||||||
|
String jsonString = null;
|
||||||
|
try {
|
||||||
|
jsonString = objectMapper.writeValueAsString(jsonNode);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return jsonString;
|
||||||
|
}
|
||||||
|
}
|
119
src/main/java/cn/octopusyan/dmt/common/util/ProcessesUtil.java
Normal file
119
src/main/java/cn/octopusyan/dmt/common/util/ProcessesUtil.java
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package cn.octopusyan.dmt.common.util;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.exec.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令工具类
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ProcessesUtil {
|
||||||
|
private static final String NEW_LINE = System.lineSeparator();
|
||||||
|
public static final int[] EXIT_VALUES = {0, 1};
|
||||||
|
|
||||||
|
private final DefaultExecutor executor;
|
||||||
|
private final ShutdownHookProcessDestroyer processDestroyer = new ShutdownHookProcessDestroyer();
|
||||||
|
private OnExecuteListener listener;
|
||||||
|
private CommandLine commandLine;
|
||||||
|
|
||||||
|
private static final Set<ProcessesUtil> set = new HashSet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent construction.
|
||||||
|
*/
|
||||||
|
private ProcessesUtil(String workingDirectory) {
|
||||||
|
this(new File(workingDirectory));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessesUtil(File workingDirectory) {
|
||||||
|
LogOutputStream logout = new LogOutputStream() {
|
||||||
|
@Override
|
||||||
|
protected void processLine(String line, int logLevel) {
|
||||||
|
if (listener != null)
|
||||||
|
listener.onExecute(line + NEW_LINE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PumpStreamHandler streamHandler = new PumpStreamHandler(logout, logout);
|
||||||
|
executor = DefaultExecutor.builder()
|
||||||
|
.setExecuteStreamHandler(streamHandler)
|
||||||
|
.setWorkingDirectory(workingDirectory)
|
||||||
|
.get();
|
||||||
|
executor.setExitValues(EXIT_VALUES);
|
||||||
|
executor.setProcessDestroyer(processDestroyer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ProcessesUtil init(String workingDirectory) {
|
||||||
|
return init(new File(workingDirectory));
|
||||||
|
}
|
||||||
|
public static ProcessesUtil init(File workingDirectory) {
|
||||||
|
ProcessesUtil util = new ProcessesUtil(workingDirectory);
|
||||||
|
set.add(util);
|
||||||
|
return util;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exec(String command) {
|
||||||
|
commandLine = CommandLine.parse(command);
|
||||||
|
try {
|
||||||
|
int execute = executor.execute(commandLine);
|
||||||
|
return Arrays.stream(EXIT_VALUES).anyMatch(item -> item == execute);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("exec error", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exec(String command, OnExecuteListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
commandLine = CommandLine.parse(command);
|
||||||
|
DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() {
|
||||||
|
@Override
|
||||||
|
public void onProcessComplete(int exitValue) {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onExecuteSuccess(Arrays.stream(EXIT_VALUES).noneMatch(item -> item == exitValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProcessFailed(ExecuteException e) {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onExecuteError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
executor.execute(commandLine, handler);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (listener != null) listener.onExecuteError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
if (processDestroyer.isEmpty()) return;
|
||||||
|
processDestroyer.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return !processDestroyer.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void destroyAll() {
|
||||||
|
set.forEach(ProcessesUtil::destroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnExecuteListener {
|
||||||
|
void onExecute(String msg);
|
||||||
|
|
||||||
|
default void onExecuteSuccess(boolean success) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onExecuteError(Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
package cn.octopusyan.dmt.common.util;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.utils.Resources;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,14 +20,16 @@ public class PropertiesUtils {
|
|||||||
/**
|
/**
|
||||||
* 主配置文件
|
* 主配置文件
|
||||||
*/
|
*/
|
||||||
private Properties properties;
|
private final Properties properties;
|
||||||
/**
|
/**
|
||||||
* 启用配置文件
|
* 启用配置文件
|
||||||
*/
|
*/
|
||||||
private Properties propertiesCustom;
|
private final Properties propertiesCustom;
|
||||||
|
|
||||||
private static PropertiesUtils propertiesUtils = new PropertiesUtils();
|
private static PropertiesUtils propertiesUtils = new PropertiesUtils();
|
||||||
|
|
||||||
|
public static final Logger logger = LoggerFactory.getLogger(PropertiesUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 私有构造,禁止直接创建
|
* 私有构造,禁止直接创建
|
||||||
*/
|
*/
|
||||||
@ -32,17 +37,18 @@ public class PropertiesUtils {
|
|||||||
// 读取配置启用的配置文件名
|
// 读取配置启用的配置文件名
|
||||||
properties = new Properties();
|
properties = new Properties();
|
||||||
propertiesCustom = new Properties();
|
propertiesCustom = new Properties();
|
||||||
InputStream in = PropertiesUtils.class.getClassLoader().getResourceAsStream("application.properties");
|
|
||||||
|
InputStream in = Resources.getResourceAsStream("application.properties");
|
||||||
try {
|
try {
|
||||||
properties.load(in);
|
properties.load(new InputStreamReader(in));
|
||||||
// 加载启用的配置
|
// 加载启用的配置
|
||||||
String property = properties.getProperty("profiles.active");
|
String property = properties.getProperty("profiles.active");
|
||||||
if (!StringUtils.isBlank(property)) {
|
if (!StringUtils.isBlank(property)) {
|
||||||
InputStream cin = PropertiesUtils.class.getClassLoader().getResourceAsStream("application-" + property + ".properties");
|
InputStream cin = Resources.getResourceAsStream("application-" + property + ".properties");
|
||||||
propertiesCustom.load(cin);
|
propertiesCustom.load(new InputStreamReader(cin));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
logger.error("读取配置文件失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,17 +64,6 @@ public class PropertiesUtils {
|
|||||||
return propertiesUtils;
|
return propertiesUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单例
|
|
||||||
*
|
|
||||||
* @return PropertiesUtils
|
|
||||||
*/
|
|
||||||
public static PropertiesUtils getInstance(File file) {
|
|
||||||
PropertiesUtils util = new PropertiesUtils();
|
|
||||||
|
|
||||||
return util;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据属性名读取值
|
* 根据属性名读取值
|
||||||
* 先去主配置查询,如果查询不到,就去启用配置查询
|
* 先去主配置查询,如果查询不到,就去启用配置查询
|
106
src/main/java/cn/octopusyan/dmt/common/util/ViewUtil.java
Normal file
106
src/main/java/cn/octopusyan/dmt/common/util/ViewUtil.java
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package cn.octopusyan.dmt.common.util;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.Application;
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Screen;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.StageStyle;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class ViewUtil {
|
||||||
|
// 获取系统缩放比
|
||||||
|
public static final double scaleX = Screen.getPrimary().getOutputScaleX();
|
||||||
|
public static final double scaleY = Screen.getPrimary().getOutputScaleY();
|
||||||
|
|
||||||
|
|
||||||
|
private static final Map<Pane, Double> paneXOffset = new HashMap<>();
|
||||||
|
private static final Map<Pane, Double> paneYOffset = new HashMap<>();
|
||||||
|
|
||||||
|
public static void bindShadow(Pane pane) {
|
||||||
|
pane.setStyle("""
|
||||||
|
-fx-background-radius: 5;
|
||||||
|
-fx-border-radius: 5;
|
||||||
|
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 15, 0, 0, 0);
|
||||||
|
-fx-background-insets: 20;
|
||||||
|
-fx-padding: 20;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bindDragged(Pane pane) {
|
||||||
|
Stage stage = getStage(pane);
|
||||||
|
bindDragged(pane, stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unbindDragged(Pane pane) {
|
||||||
|
pane.setOnMousePressed(null);
|
||||||
|
pane.setOnMouseDragged(null);
|
||||||
|
paneXOffset.remove(pane);
|
||||||
|
paneYOffset.remove(pane);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void bindDragged(Pane pane, Stage stage) {
|
||||||
|
pane.setOnMousePressed(event -> {
|
||||||
|
paneXOffset.put(pane, stage.getX() - event.getScreenX());
|
||||||
|
paneYOffset.put(pane, stage.getY() - event.getScreenY());
|
||||||
|
});
|
||||||
|
pane.setOnMouseDragged(event -> {
|
||||||
|
stage.setX(event.getScreenX() + paneXOffset.get(pane));
|
||||||
|
stage.setY(event.getScreenY() + paneYOffset.get(pane));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openDecorated(String title, String fxml) {
|
||||||
|
Stage open = Objects.requireNonNull(open(StageStyle.DECORATED, title, fxml));
|
||||||
|
open.setResizable(false);
|
||||||
|
open.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stage open(StageStyle style, String title, String fxml) {
|
||||||
|
FXMLLoader load = FxmlUtil.load(fxml);
|
||||||
|
try {
|
||||||
|
return open(style, title, (Pane) load.load());
|
||||||
|
} catch (IOException e) {
|
||||||
|
AlertUtil.getInstance().exception(e).show();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stage open(StageStyle style, String title, Pane pane) {
|
||||||
|
Stage stage = new Stage();
|
||||||
|
stage.initOwner(Application.getPrimaryStage());
|
||||||
|
stage.initStyle(style);
|
||||||
|
stage.initModality(Modality.WINDOW_MODAL);
|
||||||
|
stage.setTitle(title);
|
||||||
|
Scene scene = new Scene(pane);
|
||||||
|
scene.setUserAgentStylesheet(ConfigManager.theme().getUserAgentStylesheet());
|
||||||
|
stage.setScene(scene);
|
||||||
|
stage.sizeToScene();
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stage getStage() {
|
||||||
|
return Application.getPrimaryStage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stage getStage(Pane pane) {
|
||||||
|
try {
|
||||||
|
return (Stage) pane.getScene().getWindow();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return getStage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
448
src/main/java/cn/octopusyan/dmt/controller/MainController.java
Normal file
448
src/main/java/cn/octopusyan/dmt/controller/MainController.java
Normal file
@ -0,0 +1,448 @@
|
|||||||
|
package cn.octopusyan.dmt.controller;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.ModalPane;
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import atlantafx.base.theme.Theme;
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseController;
|
||||||
|
import cn.octopusyan.dmt.common.config.Context;
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.common.util.ClipUtil;
|
||||||
|
import cn.octopusyan.dmt.common.util.FxmlUtil;
|
||||||
|
import cn.octopusyan.dmt.common.util.ViewUtil;
|
||||||
|
import cn.octopusyan.dmt.controller.component.WordEditController;
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.task.TranslateTask;
|
||||||
|
import cn.octopusyan.dmt.view.ConsoleLog;
|
||||||
|
import cn.octopusyan.dmt.view.EditButtonTableCell;
|
||||||
|
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||||
|
import cn.octopusyan.dmt.view.filemanager.DirectoryTree;
|
||||||
|
import cn.octopusyan.dmt.viewModel.MainViewModel;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.control.cell.TextFieldTableCell;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
|
import javafx.scene.input.KeyCombination;
|
||||||
|
import javafx.scene.input.TransferMode;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.FileChooser;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主界面
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class MainController extends BaseController<MainViewModel> {
|
||||||
|
private static final ConsoleLog consoleLog = ConsoleLog.getInstance(MainController.class);
|
||||||
|
|
||||||
|
public Pane root;
|
||||||
|
|
||||||
|
public Menu viewStyle;
|
||||||
|
|
||||||
|
public static final ToggleGroup viewStyleGroup = new ToggleGroup();
|
||||||
|
|
||||||
|
public StackPane mainView;
|
||||||
|
//
|
||||||
|
public VBox translateView;
|
||||||
|
// 打开文件
|
||||||
|
public StackPane selectFileBox;
|
||||||
|
public VBox openFileView;
|
||||||
|
public VBox dragFileView;
|
||||||
|
public VBox loadFileView;
|
||||||
|
// 工具栏
|
||||||
|
public Button fileNameLabel;
|
||||||
|
public Button translate;
|
||||||
|
public ProgressBar translateProgress;
|
||||||
|
// 文件树加载
|
||||||
|
public DirectoryTree treeFileBox;
|
||||||
|
public VBox loadWordBox;
|
||||||
|
public ProgressBar loadWordProgressBar;
|
||||||
|
// 翻译界面
|
||||||
|
public Pane wordBox;
|
||||||
|
public TableView<WordItem> wordTable;
|
||||||
|
// 信息
|
||||||
|
public TitledPane titledPane;
|
||||||
|
public TextArea logArea;
|
||||||
|
|
||||||
|
public final ModalPane modalPane = new ModalPane();
|
||||||
|
// 文件选择器
|
||||||
|
public static final FileChooser fileChooser = new FileChooser();
|
||||||
|
|
||||||
|
static {
|
||||||
|
var extFilter = new FileChooser.ExtensionFilter("PBO files (*.pbo)", "*.pbo");
|
||||||
|
fileChooser.getExtensionFilters().add(extFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pane getRootPanel() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
// 信息
|
||||||
|
ConsoleLog.init(logArea);
|
||||||
|
// 界面样式
|
||||||
|
List<MenuItem> list = ConfigManager.THEME_LIST.stream().map(this::createViewStyleItem).toList();
|
||||||
|
viewStyle.getItems().addAll(list);
|
||||||
|
|
||||||
|
fileNameLabel.textProperty().bind(viewModel.fileNameProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewStyle() {
|
||||||
|
// 遮罩
|
||||||
|
getRootPanel().getChildren().add(modalPane);
|
||||||
|
modalPane.displayProperty().addListener((_, _, val) -> {
|
||||||
|
if (!val) {
|
||||||
|
modalPane.setAlignment(Pos.CENTER);
|
||||||
|
modalPane.usePredefinedTransitionFactories(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
|
||||||
|
// 文件拖拽
|
||||||
|
setDragAction(mainView);
|
||||||
|
|
||||||
|
// 复制单元格内容
|
||||||
|
Context.sceneProperty.addListener(_ -> Context.sceneProperty.get()
|
||||||
|
.getAccelerators()
|
||||||
|
.put(new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_ANY), () -> {
|
||||||
|
ObservableList<TablePosition> selectedCells = wordTable.getSelectionModel().getSelectedCells();
|
||||||
|
for (TablePosition tablePosition : selectedCells) {
|
||||||
|
Object cellData = tablePosition.getTableColumn().getCellData(tablePosition.getRow());
|
||||||
|
// 设置剪切板
|
||||||
|
ClipUtil.setClip(cellData.toString());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 日志栏清空
|
||||||
|
logArea.contextMenuProperty().addListener(_ ->
|
||||||
|
logArea.getContextMenu().getItems().addListener((ListChangeListener<MenuItem>) _ -> {
|
||||||
|
MenuItem clearLog = new MenuItem("清空");
|
||||||
|
clearLog.setOnAction(_ -> logArea.clear());
|
||||||
|
logArea.getContextMenu().getItems().add(clearLog);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置文件拖拽效果
|
||||||
|
*/
|
||||||
|
private void setDragAction(Pane fileBox) {
|
||||||
|
|
||||||
|
// 进入
|
||||||
|
fileBox.setOnDragEntered(dragEvent -> {
|
||||||
|
var dragboard = dragEvent.getDragboard();
|
||||||
|
if (dragboard.hasFiles() && isPboFile(dragboard.getFiles().getFirst())) {
|
||||||
|
selectFileBox.setVisible(true);
|
||||||
|
dragFileView.setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//离开
|
||||||
|
fileBox.setOnDragExited(_ -> {
|
||||||
|
selectFileBox.setVisible(false);
|
||||||
|
dragFileView.setVisible(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
fileBox.setOnDragOver(dragEvent -> {
|
||||||
|
var dragboard = dragEvent.getDragboard();
|
||||||
|
if (dragEvent.getGestureSource() != fileBox && dragboard.hasFiles()) {
|
||||||
|
/* allow for both copying and moving, whatever user chooses */
|
||||||
|
dragEvent.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||||
|
}
|
||||||
|
dragEvent.consume();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 松手
|
||||||
|
fileBox.setOnDragDropped(dragEvent -> {
|
||||||
|
dragFileView.setVisible(false);
|
||||||
|
|
||||||
|
var db = dragEvent.getDragboard();
|
||||||
|
boolean success = false;
|
||||||
|
var file = db.getFiles().getFirst();
|
||||||
|
if (db.hasFiles() && isPboFile(file)) {
|
||||||
|
selectFile(file);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
/* 让源知道字符串是否已成功传输和使用 */
|
||||||
|
dragEvent.setDropCompleted(success);
|
||||||
|
|
||||||
|
dragEvent.consume();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开文件选择器
|
||||||
|
*/
|
||||||
|
public void selectFile() {
|
||||||
|
selectFile(fileChooser.showOpenDialog(getWindow()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开代理设置
|
||||||
|
*/
|
||||||
|
public void openSetupProxy() {
|
||||||
|
ViewUtil.openDecorated("网络代理设置", "setup/proxy-view");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开翻译设置
|
||||||
|
*/
|
||||||
|
public void openSetupTranslate() {
|
||||||
|
ViewUtil.openDecorated("翻译设置", "setup/translate-view");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关于
|
||||||
|
*/
|
||||||
|
public void openAbout() {
|
||||||
|
ViewUtil.openDecorated("关于", "about-view");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示加载PBO文件
|
||||||
|
*/
|
||||||
|
public void onLoad() {
|
||||||
|
// 展示加载
|
||||||
|
selectFileBox.setVisible(true);
|
||||||
|
loadFileView.setVisible(true);
|
||||||
|
wordBox.getChildren().remove(wordTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示解包完成
|
||||||
|
*
|
||||||
|
* @param path 解包路径
|
||||||
|
*/
|
||||||
|
public void onUnpack(File path) {
|
||||||
|
// 加载解包目录
|
||||||
|
treeFileBox.loadRoot(path);
|
||||||
|
// 隐藏文件选择
|
||||||
|
loadFileView.setVisible(false);
|
||||||
|
selectFileBox.setVisible(false);
|
||||||
|
// 展示翻译界面
|
||||||
|
translateView.setVisible(true);
|
||||||
|
loadWordBox.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载可翻译文本数据
|
||||||
|
*
|
||||||
|
* @param wordItems 文本列表
|
||||||
|
*/
|
||||||
|
public void onLoadWord(List<WordItem> wordItems) {
|
||||||
|
loadWordBox.setVisible(false);
|
||||||
|
wordBox.setVisible(true);
|
||||||
|
bindWordTable(wordItems);
|
||||||
|
translate.setDisable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包完成
|
||||||
|
*
|
||||||
|
* @param packFile 打包临时文件
|
||||||
|
*/
|
||||||
|
public void onPackOver(File packFile) {
|
||||||
|
// 选择文件保存地址
|
||||||
|
fileChooser.setInitialFileName(packFile.getName());
|
||||||
|
File file = fileChooser.showSaveDialog(getWindow());
|
||||||
|
|
||||||
|
if (file == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (file.exists()) {
|
||||||
|
//文件已存在,则删除覆盖文件
|
||||||
|
FileUtils.deleteQuietly(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
String exportFilePath = file.getAbsolutePath();
|
||||||
|
consoleLog.info(STR."导出文件路径 => \{exportFilePath}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileUtils.copyFile(packFile, file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
consoleLog.error("保存文件失败!", e);
|
||||||
|
Platform.runLater(() -> AlertUtil.getInstance(getWindow()).exception(e).content("保存文件失败!").show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTranslate() {
|
||||||
|
viewModel.startTranslate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startPack() {
|
||||||
|
viewModel.pack();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectAllLog() {
|
||||||
|
logArea.selectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyLog() {
|
||||||
|
logArea.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearLog() {
|
||||||
|
logArea.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================{ }========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开文件
|
||||||
|
*/
|
||||||
|
private void selectFile(File file) {
|
||||||
|
viewModel.selectFile(file);
|
||||||
|
viewModel.unpack();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定表格数据
|
||||||
|
*
|
||||||
|
* @param words 单词列表
|
||||||
|
*/
|
||||||
|
private void bindWordTable(List<WordItem> words) {
|
||||||
|
|
||||||
|
if (wordTable == null) {
|
||||||
|
wordTable = new TableView<>();
|
||||||
|
// 填满
|
||||||
|
VBox.setVgrow(wordTable, Priority.ALWAYS);
|
||||||
|
// 可编辑
|
||||||
|
wordTable.setEditable(true);
|
||||||
|
// 自动调整列宽
|
||||||
|
wordTable.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||||
|
// 边框
|
||||||
|
Styles.toggleStyleClass(wordTable, Styles.BORDERED);
|
||||||
|
// // 行分隔
|
||||||
|
// Styles.toggleStyleClass(wordTable, Styles.STRIPED);
|
||||||
|
// 单元格选择模式而不是行选择
|
||||||
|
wordTable.getSelectionModel().setCellSelectionEnabled(true);
|
||||||
|
// 不允许选择多个单元格
|
||||||
|
wordTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||||
|
|
||||||
|
// 创建列
|
||||||
|
TableColumn<WordItem, String> colFile = createColumn("文件");
|
||||||
|
colFile.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getFile().getName()));
|
||||||
|
TableColumn<WordItem, String> colOriginal = createColumn("原文");
|
||||||
|
colOriginal.setCellValueFactory(param -> param.getValue().getOriginalProperty());
|
||||||
|
TableColumn<WordItem, String> colChinese = createColumn("中文翻译");
|
||||||
|
colChinese.setCellValueFactory(param -> param.getValue().getChineseProperty());
|
||||||
|
colChinese.setEditable(true);
|
||||||
|
TableColumn<WordItem, WordItem> colIcon = new TableColumn<>("");
|
||||||
|
colIcon.setSortable(false);
|
||||||
|
colIcon.setCellFactory(EditButtonTableCell.forTableColumn(item -> {
|
||||||
|
// 展示编辑弹窗
|
||||||
|
try {
|
||||||
|
showModal(getEditWordPane(item), false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
consoleLog.error("加载布局失败", e);
|
||||||
|
}
|
||||||
|
}, item -> {
|
||||||
|
// 翻译当前文本
|
||||||
|
new TranslateTask(Collections.singletonList(item)).execute();
|
||||||
|
}));
|
||||||
|
|
||||||
|
wordTable.getColumns().add(colFile);
|
||||||
|
wordTable.getColumns().add(colOriginal);
|
||||||
|
wordTable.getColumns().add(colChinese);
|
||||||
|
wordTable.getColumns().add(colIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加表数据
|
||||||
|
wordTable.getItems().clear();
|
||||||
|
wordBox.getChildren().addFirst(wordTable);
|
||||||
|
wordTable.getItems().addAll(words);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表字段创建
|
||||||
|
*
|
||||||
|
* @param colName 列名
|
||||||
|
* @return 列定义
|
||||||
|
*/
|
||||||
|
private TableColumn<WordItem, String> createColumn(String colName) {
|
||||||
|
TableColumn<WordItem, String> tableColumn = new TableColumn<>(colName);
|
||||||
|
tableColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||||
|
tableColumn.setPrefWidth(150);
|
||||||
|
tableColumn.setSortable(false);
|
||||||
|
tableColumn.setEditable("中文翻译".equals(colName));
|
||||||
|
return tableColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuItem createViewStyleItem(Theme theme) {
|
||||||
|
var item = new RadioMenuItem(theme.getName());
|
||||||
|
item.setSelected(theme.getName().equals(ConfigManager.themeName()));
|
||||||
|
item.setToggleGroup(viewStyleGroup);
|
||||||
|
item.setUserData(theme);
|
||||||
|
item.selectedProperty().subscribe(selected -> {
|
||||||
|
if (!selected) return;
|
||||||
|
ConfigManager.theme(theme);
|
||||||
|
});
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否PBO文件
|
||||||
|
*/
|
||||||
|
private boolean isPboFile(File file) {
|
||||||
|
if (file == null) return false;
|
||||||
|
return Pattern.compile(".*(.pbo)$").matcher(file.getName()).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展示遮罩弹窗
|
||||||
|
* <p>
|
||||||
|
* 当{@code persistent}为{@code true}时,需要调用{@link #hideModal}才能关闭
|
||||||
|
*
|
||||||
|
* @param node 展示内容
|
||||||
|
* @param persistent 是否持久性内容
|
||||||
|
*/
|
||||||
|
private void showModal(Node node, boolean persistent) {
|
||||||
|
modalPane.setAlignment(Pos.CENTER);
|
||||||
|
modalPane.usePredefinedTransitionFactories(null);
|
||||||
|
modalPane.show(node);
|
||||||
|
modalPane.setPersistent(persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭/隐藏遮罩弹窗
|
||||||
|
*/
|
||||||
|
public void hideModal() {
|
||||||
|
modalPane.hide(false);
|
||||||
|
modalPane.setPersistent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编辑
|
||||||
|
*/
|
||||||
|
private Node getEditWordPane(WordItem data) throws IOException {
|
||||||
|
FXMLLoader load = FxmlUtil.load("component/edit-view");
|
||||||
|
Pane pane = load.load();
|
||||||
|
WordEditController ctrl = load.getController();
|
||||||
|
ctrl.bindData(data);
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package cn.octopusyan.dmt.controller.component;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseController;
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.common.manager.thread.ThreadPoolManager;
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.translate.factory.TranslateFactoryImpl;
|
||||||
|
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||||
|
import cn.octopusyan.dmt.viewModel.WordEditViewModel;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文本编辑
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class WordEditController extends BaseController<WordEditViewModel> {
|
||||||
|
|
||||||
|
public VBox root;
|
||||||
|
public TextArea original;
|
||||||
|
public Button translate;
|
||||||
|
public TextArea chinese;
|
||||||
|
public ProgressIndicator progress;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pane getRootPanel() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bindData(WordItem data) {
|
||||||
|
viewModel.setData(data);
|
||||||
|
original.textProperty().bind(viewModel.getOriginalProperty());
|
||||||
|
chinese.textProperty().bindBidirectional(viewModel.getChineseProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startTranslate() {
|
||||||
|
progress.setVisible(true);
|
||||||
|
ThreadPoolManager.getInstance().execute(() -> {
|
||||||
|
try {
|
||||||
|
String result = TranslateFactoryImpl.getInstance().translate(ConfigManager.translateApi(), original.getText());
|
||||||
|
Platform.runLater(() -> chinese.setText(result));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Platform.runLater(() -> AlertUtil.getInstance(getWindow()).exception(e).show());
|
||||||
|
}
|
||||||
|
Platform.runLater(() -> progress.setVisible(false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package cn.octopusyan.dmt.controller.help;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseController;
|
||||||
|
import cn.octopusyan.dmt.common.config.Constants;
|
||||||
|
import cn.octopusyan.dmt.common.config.Context;
|
||||||
|
import cn.octopusyan.dmt.viewModel.AboutViewModel;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关于
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class AboutController extends BaseController<AboutViewModel> {
|
||||||
|
public VBox root;
|
||||||
|
public Label title;
|
||||||
|
public Label infoTitle;
|
||||||
|
public Label version;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pane getRootPanel() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
title.setText(Constants.APP_TITLE);
|
||||||
|
infoTitle.setText(Constants.APP_TITLE);
|
||||||
|
version.setText(STR."版本:\{Constants.APP_VERSION}(x64)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openGitee() {
|
||||||
|
Context.openUrl("https://gitee.com/octopus_yan/dayz-mod-translator");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openGithub() {
|
||||||
|
Context.openUrl("https://github.com/octopusYan/dayz-mod-translator");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
package cn.octopusyan.dmt.controller.setup;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseController;
|
||||||
|
import cn.octopusyan.dmt.common.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.viewModel.ProxyViewModel;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.scene.control.RadioButton;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.ToggleGroup;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class ProxyController extends BaseController<ProxyViewModel> {
|
||||||
|
public VBox root;
|
||||||
|
|
||||||
|
public RadioButton noneProxy;
|
||||||
|
public RadioButton systemProxy;
|
||||||
|
public RadioButton manualProxy;
|
||||||
|
public ToggleGroup proxyGroup = new ToggleGroup();
|
||||||
|
|
||||||
|
public GridPane manualProxyView;
|
||||||
|
public TextField proxyHost;
|
||||||
|
public TextField proxyPort;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pane getRootPanel() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
noneProxy.setUserData(ProxySetup.NO_PROXY);
|
||||||
|
systemProxy.setUserData(ProxySetup.SYSTEM);
|
||||||
|
manualProxy.setUserData(ProxySetup.MANUAL);
|
||||||
|
|
||||||
|
noneProxy.setToggleGroup(proxyGroup);
|
||||||
|
systemProxy.setToggleGroup(proxyGroup);
|
||||||
|
manualProxy.setToggleGroup(proxyGroup);
|
||||||
|
|
||||||
|
manualProxyView.disableProperty().bind(
|
||||||
|
Bindings.createBooleanBinding(() -> !manualProxy.selectedProperty().get(), manualProxy.selectedProperty())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
proxyGroup.selectedToggleProperty().addListener((_, _, value) -> {
|
||||||
|
viewModel.proxySetupProperty().set((ProxySetup) value.getUserData());
|
||||||
|
});
|
||||||
|
proxyGroup.selectToggle(switch (ConfigManager.proxySetup()) {
|
||||||
|
case ProxySetup.SYSTEM -> systemProxy;
|
||||||
|
case ProxySetup.MANUAL -> manualProxy;
|
||||||
|
default -> noneProxy;
|
||||||
|
});
|
||||||
|
|
||||||
|
proxyHost.textProperty().bindBidirectional(viewModel.proxyHostProperty());
|
||||||
|
proxyPort.textProperty().bindBidirectional(viewModel.proxyPortProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void proxyTest() {
|
||||||
|
viewModel.proxyTest();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package cn.octopusyan.dmt.controller.setup;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseController;
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||||
|
import cn.octopusyan.dmt.viewModel.TranslateViewModel;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class TranslateController extends BaseController<TranslateViewModel> {
|
||||||
|
public VBox root;
|
||||||
|
public ComboBox<TranslateApi> translateSourceCombo;
|
||||||
|
public TextField qps;
|
||||||
|
public VBox appidBox;
|
||||||
|
public TextField appid;
|
||||||
|
public VBox apikeyBox;
|
||||||
|
public TextField apikey;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pane getRootPanel() {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
|
||||||
|
// 翻译源
|
||||||
|
for (TranslateApi value : TranslateApi.values()) {
|
||||||
|
ObservableList<TranslateApi> items = translateSourceCombo.getItems();
|
||||||
|
items.addAll(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
translateSourceCombo.setConverter(new StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(TranslateApi object) {
|
||||||
|
if (object == null) return null;
|
||||||
|
|
||||||
|
return object.getLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TranslateApi fromString(String string) {
|
||||||
|
return TranslateApi.getByLabel(string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当前翻译源
|
||||||
|
translateSourceCombo.getSelectionModel().select(ConfigManager.translateApi());
|
||||||
|
viewModel.getSource().bind(translateSourceCombo.getSelectionModel().selectedItemProperty());
|
||||||
|
|
||||||
|
qps.textProperty().bindBidirectional(viewModel.getQps());
|
||||||
|
appid.textProperty().bindBidirectional(viewModel.getAppId());
|
||||||
|
apikey.textProperty().bindBidirectional(viewModel.getApiKey());
|
||||||
|
|
||||||
|
appidBox.visibleProperty().bind(viewModel.getNeedApiKey());
|
||||||
|
apikeyBox.visibleProperty().bind(viewModel.getNeedApiKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
TranslateApi source = translateSourceCombo.getValue();
|
||||||
|
String apikey = this.apikey.getText();
|
||||||
|
String appid = this.appid.getText();
|
||||||
|
int qps = Integer.parseInt(this.qps.getText());
|
||||||
|
|
||||||
|
ConfigManager.translateApi(source);
|
||||||
|
ConfigManager.translateQps(source, qps);
|
||||||
|
if (source.needApiKey()) {
|
||||||
|
if (StringUtils.isBlank(apikey) || StringUtils.isBlank(appid)) {
|
||||||
|
AlertUtil.getInstance(getWindow()).error("认证信息不能为空").show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.translateApikey(source, apikey);
|
||||||
|
ConfigManager.translateAppid(source, appid);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy();
|
||||||
|
}
|
||||||
|
}
|
33
src/main/java/cn/octopusyan/dmt/model/ConfigModel.java
Normal file
33
src/main/java/cn/octopusyan/dmt/model/ConfigModel.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package cn.octopusyan.dmt.model;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI配置信息
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ConfigModel {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ConfigModel.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主题
|
||||||
|
*/
|
||||||
|
private String theme = ConfigManager.DEFAULT_THEME;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理设置
|
||||||
|
*/
|
||||||
|
private ProxyInfo proxy = new ProxyInfo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理设置
|
||||||
|
*/
|
||||||
|
private Translate translate = new Translate();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
39
src/main/java/cn/octopusyan/dmt/model/ProxyInfo.java
Normal file
39
src/main/java/cn/octopusyan/dmt/model/ProxyInfo.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package cn.octopusyan.dmt.model;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.enums.ProxySetup;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理信息
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ProxyInfo {
|
||||||
|
/**
|
||||||
|
* 主机地址
|
||||||
|
*/
|
||||||
|
private String host = "";
|
||||||
|
/**
|
||||||
|
* 端口
|
||||||
|
*/
|
||||||
|
private String port = "";
|
||||||
|
/**
|
||||||
|
* 登录名
|
||||||
|
*/
|
||||||
|
private String username = "";
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
private String password = "";
|
||||||
|
/**
|
||||||
|
* 测试Url
|
||||||
|
*/
|
||||||
|
private String testUrl = "http://";
|
||||||
|
/**
|
||||||
|
* 代理类型
|
||||||
|
*
|
||||||
|
* @see ProxySetup
|
||||||
|
*/
|
||||||
|
private String setup = ProxySetup.NO_PROXY.getCode();
|
||||||
|
}
|
52
src/main/java/cn/octopusyan/dmt/model/Translate.java
Normal file
52
src/main/java/cn/octopusyan/dmt/model/Translate.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package cn.octopusyan.dmt.model;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译配置
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Translate {
|
||||||
|
/**
|
||||||
|
* 当前使用接口
|
||||||
|
*/
|
||||||
|
private String use = TranslateApi.FREE_BAIDU.getName();
|
||||||
|
/**
|
||||||
|
* 接口配置
|
||||||
|
*/
|
||||||
|
private Map<String, Config> config = new HashMap<>() {
|
||||||
|
{
|
||||||
|
// 初始化
|
||||||
|
for (TranslateApi api : TranslateApi.values()) {
|
||||||
|
put(api.getName(), new Config("", "", api.getDefaultQps()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class Config {
|
||||||
|
/**
|
||||||
|
* api key
|
||||||
|
*/
|
||||||
|
private String appId;
|
||||||
|
/**
|
||||||
|
* api 密钥
|
||||||
|
*/
|
||||||
|
private String secretKey;
|
||||||
|
/**
|
||||||
|
* 请求速率
|
||||||
|
*/
|
||||||
|
private Integer qps;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
29
src/main/java/cn/octopusyan/dmt/model/UpgradeConfig.java
Normal file
29
src/main/java/cn/octopusyan/dmt/model/UpgradeConfig.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package cn.octopusyan.dmt.model;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.util.PropertiesUtils;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UpgradeConfig {
|
||||||
|
|
||||||
|
private final String owner = "octopusYan";
|
||||||
|
|
||||||
|
private final String repo = "dayz-mod-translator";
|
||||||
|
|
||||||
|
private String releaseFile = "DMT-windows-nojre.zip";
|
||||||
|
|
||||||
|
private String version = PropertiesUtils.getInstance().getProperty("app.version");
|
||||||
|
|
||||||
|
public String getReleaseApi() {
|
||||||
|
return STR."https://api.github.com/repos/\{getOwner()}/\{getRepo()}/releases/latest";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDownloadUrl(String version) {
|
||||||
|
return STR."https://github.com/\{getOwner()}/\{getRepo()}/releases/download/\{version}/\{getReleaseFile()}";
|
||||||
|
}
|
||||||
|
}
|
52
src/main/java/cn/octopusyan/dmt/model/WordItem.java
Normal file
52
src/main/java/cn/octopusyan/dmt/model/WordItem.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package cn.octopusyan.dmt.model;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译文本
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WordItem {
|
||||||
|
/**
|
||||||
|
* 所在文件
|
||||||
|
*/
|
||||||
|
private File file;
|
||||||
|
/**
|
||||||
|
* 行数
|
||||||
|
*/
|
||||||
|
private Integer lines;
|
||||||
|
/**
|
||||||
|
* 开始下标
|
||||||
|
*/
|
||||||
|
private Integer index;
|
||||||
|
/**
|
||||||
|
* 原文
|
||||||
|
*/
|
||||||
|
private StringProperty originalProperty = new SimpleStringProperty();
|
||||||
|
/**
|
||||||
|
* 中文
|
||||||
|
*/
|
||||||
|
private StringProperty chineseProperty = new SimpleStringProperty();
|
||||||
|
|
||||||
|
public WordItem(File file, Integer lines, Integer index, String original, String chinese) {
|
||||||
|
this.file = file;
|
||||||
|
this.lines = lines;
|
||||||
|
this.index = index;
|
||||||
|
this.originalProperty.set(original);
|
||||||
|
this.chineseProperty.set(chinese);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChinese() {
|
||||||
|
return chineseProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOriginal() {
|
||||||
|
return originalProperty.get();
|
||||||
|
}
|
||||||
|
}
|
80
src/main/java/cn/octopusyan/dmt/task/PackTask.java
Normal file
80
src/main/java/cn/octopusyan/dmt/task/PackTask.java
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package cn.octopusyan.dmt.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.task.base.BaseTask;
|
||||||
|
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||||
|
import cn.octopusyan.dmt.utils.PBOUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collector;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包任务
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class PackTask extends BaseTask<PackTask.PackListener> {
|
||||||
|
|
||||||
|
private static final Function<List<WordItem>, List<WordItem>> sortFunc = items -> items.stream().sorted(Comparator.comparing(WordItem::getLines)).toList();
|
||||||
|
private static final Collector<WordItem, Object, List<WordItem>> downstream = Collectors.collectingAndThen(Collectors.toList(), sortFunc);
|
||||||
|
|
||||||
|
private final Map<File, List<WordItem>> wordFileMap;
|
||||||
|
private final String unpackPath;
|
||||||
|
|
||||||
|
public PackTask(List<WordItem> words, String unpackPath) {
|
||||||
|
super("Pack");
|
||||||
|
|
||||||
|
if (words == null)
|
||||||
|
throw new RuntimeException("参数为null!");
|
||||||
|
|
||||||
|
this.unpackPath = unpackPath;
|
||||||
|
wordFileMap = words.stream().collect(Collectors.groupingBy(WordItem::getFile, downstream));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void task() throws Exception {
|
||||||
|
if (wordFileMap.isEmpty()) return;
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
PBOUtil.writeWords(wordFileMap);
|
||||||
|
if (listener != null) listener.onWriteOver();
|
||||||
|
// 打包
|
||||||
|
File packFile = PBOUtil.pack(unpackPath);
|
||||||
|
if (listener != null) listener.onPackOver(packFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包监听
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public abstract static class PackListener extends DefaultTaskListener {
|
||||||
|
|
||||||
|
public PackListener() {
|
||||||
|
super(true);
|
||||||
|
getProgress().setWidth(550);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSucceed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入完成
|
||||||
|
*/
|
||||||
|
public abstract void onWriteOver();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包完成
|
||||||
|
*
|
||||||
|
* @param file 文件地址
|
||||||
|
*/
|
||||||
|
public abstract void onPackOver(File file);
|
||||||
|
}
|
||||||
|
}
|
28
src/main/java/cn/octopusyan/dmt/task/ProxyCheckTask.java
Normal file
28
src/main/java/cn/octopusyan/dmt/task/ProxyCheckTask.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package cn.octopusyan.dmt.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.dmt.task.base.BaseTask;
|
||||||
|
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理检测任务
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ProxyCheckTask extends BaseTask<DefaultTaskListener> {
|
||||||
|
private final String checkUrl;
|
||||||
|
|
||||||
|
public ProxyCheckTask(String checkUrl) {
|
||||||
|
super(STR."ProxyCheck[\{checkUrl}]");
|
||||||
|
this.checkUrl = checkUrl;
|
||||||
|
this.updateProgress(0d, 1d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void task() throws Exception {
|
||||||
|
String response = HttpUtil.getInstance().get(checkUrl, null, null);
|
||||||
|
log.debug(STR."Proxy check response result => \n\{response}");
|
||||||
|
}
|
||||||
|
}
|
91
src/main/java/cn/octopusyan/dmt/task/TranslateTask.java
Normal file
91
src/main/java/cn/octopusyan/dmt/task/TranslateTask.java
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package cn.octopusyan.dmt.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.thread.ThreadPoolManager;
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.task.base.BaseTask;
|
||||||
|
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||||
|
import cn.octopusyan.dmt.translate.DelayWord;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateUtil;
|
||||||
|
import cn.octopusyan.dmt.view.ConsoleLog;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.DelayQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译工具
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public class TranslateTask extends BaseTask<DefaultTaskListener> {
|
||||||
|
|
||||||
|
public static final ConsoleLog consoleLog = ConsoleLog.getInstance(TranslateTask.class);
|
||||||
|
@Getter
|
||||||
|
private final ThreadPoolManager threadPoolManager = ThreadPoolManager.getInstance("translate-pool");
|
||||||
|
private final DelayQueue<DelayWord> delayQueue;
|
||||||
|
private final long total;
|
||||||
|
private final AtomicLong quantity = new AtomicLong();
|
||||||
|
private final CountDownLatch countDownLatch;
|
||||||
|
|
||||||
|
public TranslateTask(List<WordItem> data) {
|
||||||
|
this(TranslateUtil.getDelayQueue(data), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TranslateTask(DelayQueue<DelayWord> queue, int total) {
|
||||||
|
super("Translate");
|
||||||
|
this.delayQueue = queue;
|
||||||
|
this.total = total;
|
||||||
|
this.quantity.set(total - delayQueue.size());
|
||||||
|
countDownLatch = new CountDownLatch(queue.size());
|
||||||
|
|
||||||
|
updateProgress(quantity.get(), total);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void task() throws Exception {
|
||||||
|
|
||||||
|
while (!delayQueue.isEmpty() && !isCancelled()) {
|
||||||
|
// 取出文本
|
||||||
|
DelayWord word = delayQueue.take();
|
||||||
|
|
||||||
|
// 多线程处理
|
||||||
|
threadPoolManager.execute(() -> {
|
||||||
|
// 翻译
|
||||||
|
try {
|
||||||
|
if (total == 1) {
|
||||||
|
consoleLog.info("正在翻译:{}", word.getWord().getOriginal());
|
||||||
|
}
|
||||||
|
|
||||||
|
String translate = TranslateUtil.translate(word.getApi(), word.getWord().getOriginal());
|
||||||
|
// 回调监听器
|
||||||
|
if (StringUtils.isEmpty(translate)) return;
|
||||||
|
|
||||||
|
synchronized (quantity) {
|
||||||
|
long progress = quantity.addAndGet(1);
|
||||||
|
// 设置翻译结果
|
||||||
|
Platform.runLater(() -> word.getWord().getChineseProperty().setValue(translate));
|
||||||
|
// 更新进度
|
||||||
|
updateProgress(progress, total);
|
||||||
|
// 输出信息
|
||||||
|
if (total != 1) {
|
||||||
|
consoleLog.info("正在翻译({}/{})", progress, total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!(e instanceof InterruptedException) || !isCancelled()) {
|
||||||
|
consoleLog.error("翻译失败", e);
|
||||||
|
}
|
||||||
|
delayQueue.add(word);
|
||||||
|
}
|
||||||
|
countDownLatch.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
countDownLatch.await();
|
||||||
|
}
|
||||||
|
}
|
63
src/main/java/cn/octopusyan/dmt/task/UnpackTask.java
Normal file
63
src/main/java/cn/octopusyan/dmt/task/UnpackTask.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package cn.octopusyan.dmt.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.task.base.BaseTask;
|
||||||
|
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||||
|
import cn.octopusyan.dmt.utils.PBOUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包PBO文件任务
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class UnpackTask extends BaseTask<UnpackTask.UnpackListener> {
|
||||||
|
|
||||||
|
private final File pboFile;
|
||||||
|
|
||||||
|
public UnpackTask(File pboFile) {
|
||||||
|
super("Unpack " + pboFile.getName());
|
||||||
|
this.pboFile = pboFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void task() throws Exception {
|
||||||
|
// 解包
|
||||||
|
String path = PBOUtil.unpack(pboFile);
|
||||||
|
if (listener != null)
|
||||||
|
listener.onUnpackOver(path);
|
||||||
|
|
||||||
|
List<WordItem> wordItems = PBOUtil.findWord(path);
|
||||||
|
if (listener != null)
|
||||||
|
listener.onFindWordOver(wordItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包监听
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public abstract static class UnpackListener extends DefaultTaskListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSucceed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包完成
|
||||||
|
*
|
||||||
|
* @param path 输出路径
|
||||||
|
*/
|
||||||
|
public abstract void onUnpackOver(String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找可翻译文本
|
||||||
|
*
|
||||||
|
* @param wordItems 可翻译文本列表
|
||||||
|
*/
|
||||||
|
public abstract void onFindWordOver(List<WordItem> wordItems);
|
||||||
|
}
|
||||||
|
}
|
55
src/main/java/cn/octopusyan/dmt/task/UpgradeTask.java
Normal file
55
src/main/java/cn/octopusyan/dmt/task/UpgradeTask.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package cn.octopusyan.dmt.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.dmt.common.util.JsonUtil;
|
||||||
|
import cn.octopusyan.dmt.model.UpgradeConfig;
|
||||||
|
import cn.octopusyan.dmt.task.base.BaseTask;
|
||||||
|
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查更新任务
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class UpgradeTask extends BaseTask<UpgradeTask.UpgradeListener> {
|
||||||
|
|
||||||
|
private final UpgradeConfig upgradeConfig = ConfigManager.upgradeConfig();
|
||||||
|
|
||||||
|
protected UpgradeTask() {
|
||||||
|
super("Check Update");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void task() throws Exception {
|
||||||
|
String responseStr = HttpUtil.getInstance().get(upgradeConfig.getReleaseApi(), null, null);
|
||||||
|
JsonNode response = JsonUtil.parseJsonObject(responseStr);
|
||||||
|
|
||||||
|
// TODO 校验返回内容
|
||||||
|
String newVersion = response.get("tag_name").asText();
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.onChecked(!StringUtils.equals(upgradeConfig.getVersion(), newVersion), newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查更新监听默认实现
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public abstract static class UpgradeListener extends DefaultTaskListener {
|
||||||
|
|
||||||
|
public UpgradeListener() {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onChecked(boolean hasUpgrade, String version);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSucceed() {
|
||||||
|
// do nothing ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/main/java/cn/octopusyan/dmt/task/base/BaseTask.java
Normal file
50
src/main/java/cn/octopusyan/dmt/task/base/BaseTask.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package cn.octopusyan.dmt.task.base;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.thread.ThreadPoolManager;
|
||||||
|
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public abstract class BaseTask<T extends Listener> extends Task<Void> {
|
||||||
|
private final ThreadPoolManager Executor = ThreadPoolManager.getInstance("task-pool");
|
||||||
|
protected T listener;
|
||||||
|
@Getter
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
protected BaseTask(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameTag() {
|
||||||
|
return "Task " + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void call() throws Exception {
|
||||||
|
if (listener != null) listener.onStart();
|
||||||
|
task();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void task() throws Exception;
|
||||||
|
|
||||||
|
public void onListen(T listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
if (this.listener == null)
|
||||||
|
return;
|
||||||
|
if (listener instanceof DefaultTaskListener lis)
|
||||||
|
lis.setTask((BaseTask) this);
|
||||||
|
|
||||||
|
setOnRunning(_ -> listener.onRunning());
|
||||||
|
setOnCancelled(_ -> listener.onCancelled());
|
||||||
|
setOnFailed(_ -> listener.onFailed(getException()));
|
||||||
|
setOnSucceeded(_ -> listener.onSucceeded());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() {
|
||||||
|
Executor.execute(this);
|
||||||
|
}
|
||||||
|
}
|
23
src/main/java/cn/octopusyan/dmt/task/base/Listener.java
Normal file
23
src/main/java/cn/octopusyan/dmt/task/base/Listener.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package cn.octopusyan.dmt.task.base;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务监听
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public interface Listener {
|
||||||
|
|
||||||
|
default void onStart() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onRunning() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onCancelled() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onFailed(Throwable throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSucceeded();
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package cn.octopusyan.dmt.task.listener;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.task.base.BaseTask;
|
||||||
|
import cn.octopusyan.dmt.task.base.Listener;
|
||||||
|
import cn.octopusyan.dmt.view.ConsoleLog;
|
||||||
|
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||||
|
import cn.octopusyan.dmt.view.alert.builder.ProgressBuilder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务监听器默认实现
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class DefaultTaskListener implements Listener {
|
||||||
|
private ConsoleLog consoleLog;
|
||||||
|
@Getter
|
||||||
|
private BaseTask<? extends DefaultTaskListener> task;
|
||||||
|
/**
|
||||||
|
* 加载弹窗
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
final ProgressBuilder progress = AlertUtil.getInstance().progress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否展示加载弹窗
|
||||||
|
*/
|
||||||
|
private final boolean showProgress;
|
||||||
|
|
||||||
|
public DefaultTaskListener() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultTaskListener(boolean showProgress) {
|
||||||
|
this.showProgress = showProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <L extends DefaultTaskListener> void setTask(BaseTask<L> task) {
|
||||||
|
this.task = task;
|
||||||
|
consoleLog = ConsoleLog.getInstance(task.getClass().getSimpleName());
|
||||||
|
progress.onCancel(task::cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
consoleLog.info(STR."\{task.getNameTag()} start ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRunning() {
|
||||||
|
// 展示加载弹窗
|
||||||
|
if (showProgress)
|
||||||
|
progress.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled() {
|
||||||
|
progress.close();
|
||||||
|
consoleLog.info(STR."\{task.getNameTag()} cancel ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailed(Throwable throwable) {
|
||||||
|
progress.close();
|
||||||
|
consoleLog.error(STR."\{task.getNameTag()} fail ...", throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSucceeded() {
|
||||||
|
progress.close();
|
||||||
|
consoleLog.info(STR."\{task.getNameTag()} success ...");
|
||||||
|
onSucceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void onSucceed();
|
||||||
|
}
|
10
src/main/java/cn/octopusyan/dmt/translate/ApiKey.java
Normal file
10
src/main/java/cn/octopusyan/dmt/translate/ApiKey.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package cn.octopusyan.dmt.translate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 密钥配置
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public record ApiKey(String appid, String apiKey) {
|
||||||
|
|
||||||
|
}
|
39
src/main/java/cn/octopusyan/dmt/translate/DelayWord.java
Normal file
39
src/main/java/cn/octopusyan/dmt/translate/DelayWord.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package cn.octopusyan.dmt.translate;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.concurrent.Delayed;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延迟翻译对象
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class DelayWord implements Delayed {
|
||||||
|
@Setter
|
||||||
|
private TranslateApi api;
|
||||||
|
private final WordItem word;
|
||||||
|
private long delayTime;
|
||||||
|
|
||||||
|
public DelayWord(WordItem word) {
|
||||||
|
this.word = word;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDelayTime(long time, TimeUnit timeUnit) {
|
||||||
|
this.delayTime = System.currentTimeMillis() + (time > 0 ? TimeUnit.MILLISECONDS.convert(time, timeUnit) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDelay(TimeUnit unit) {
|
||||||
|
return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Delayed o) {
|
||||||
|
return Long.compare(this.delayTime, ((DelayWord) o).delayTime);
|
||||||
|
}
|
||||||
|
}
|
60
src/main/java/cn/octopusyan/dmt/translate/TranslateApi.java
Normal file
60
src/main/java/cn/octopusyan/dmt/translate/TranslateApi.java
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package cn.octopusyan.dmt.translate;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.model.Translate;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译引擎类型
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum TranslateApi {
|
||||||
|
FREE_BAIDU("free_baidu", "百度", false),
|
||||||
|
FREE_GOOGLE("free_google", "谷歌", false),
|
||||||
|
BAIDU("baidu", "百度(需认证)", true),
|
||||||
|
;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String name;
|
||||||
|
@Getter
|
||||||
|
private final String label;
|
||||||
|
private final boolean needApiKey;
|
||||||
|
private final Integer defaultQps;
|
||||||
|
|
||||||
|
TranslateApi(String name, String label, boolean needApiKey) {
|
||||||
|
// 设置接口默认qps=10
|
||||||
|
this(name, label, needApiKey, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslateApi(String name, String label, boolean needApiKey, int defaultQps) {
|
||||||
|
this.name = name;
|
||||||
|
this.label = label;
|
||||||
|
this.needApiKey = needApiKey;
|
||||||
|
this.defaultQps = defaultQps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean needApiKey() {
|
||||||
|
return needApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Translate.Config translate() {
|
||||||
|
return new Translate.Config("", "", defaultQps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TranslateApi get(String name) {
|
||||||
|
for (TranslateApi value : values()) {
|
||||||
|
if (value.getName().equals(name))
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("类型不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TranslateApi getByLabel(String label) {
|
||||||
|
for (TranslateApi value : values()) {
|
||||||
|
if (value.getLabel().equals(label))
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("类型不存在");
|
||||||
|
}
|
||||||
|
}
|
66
src/main/java/cn/octopusyan/dmt/translate/TranslateUtil.java
Normal file
66
src/main/java/cn/octopusyan/dmt/translate/TranslateUtil.java
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package cn.octopusyan.dmt.translate;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.translate.factory.TranslateFactory;
|
||||||
|
import cn.octopusyan.dmt.translate.factory.TranslateFactoryImpl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.DelayQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class TranslateUtil {
|
||||||
|
private static final TranslateFactory factory = TranslateFactoryImpl.getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译(英->中)
|
||||||
|
* <p> TODO 切换语种
|
||||||
|
*
|
||||||
|
* @param api 翻译源
|
||||||
|
* @param sourceString 原始文本
|
||||||
|
* @return 翻译结果
|
||||||
|
* @throws Exception 翻译出错
|
||||||
|
*/
|
||||||
|
public static String translate(TranslateApi api, String sourceString) throws Exception {
|
||||||
|
return factory.translate(api, sourceString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String translate(String sourceString) throws Exception {
|
||||||
|
return factory.translate(ConfigManager.translateApi(), sourceString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取延迟翻译对象
|
||||||
|
*
|
||||||
|
* @param words 待翻译文本列表
|
||||||
|
* @return 延迟对象
|
||||||
|
*/
|
||||||
|
public static DelayQueue<DelayWord> getDelayQueue(List<WordItem> words) {
|
||||||
|
return getDelayQueue(ConfigManager.translateApi(), words);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取延迟翻译对象
|
||||||
|
*
|
||||||
|
* @param source 翻译接口
|
||||||
|
* @param words 待翻译文本列表
|
||||||
|
* @return 延迟对象
|
||||||
|
*/
|
||||||
|
public static DelayQueue<DelayWord> getDelayQueue(TranslateApi source, List<WordItem> words) {
|
||||||
|
return factory.getDelayQueue(source, words);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重设延迟时间
|
||||||
|
*
|
||||||
|
* @param index 序列号
|
||||||
|
* @param delayWord 延迟对象
|
||||||
|
*/
|
||||||
|
public static void resetDelayTime(int index, DelayWord delayWord) {
|
||||||
|
factory.resetDelayTime(ConfigManager.translateApi(), index, delayWord);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package cn.octopusyan.dmt.translate.factory;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.translate.DelayWord;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.DelayQueue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译器接口
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public interface TranslateFactory {
|
||||||
|
/**
|
||||||
|
* 翻译处理
|
||||||
|
*
|
||||||
|
* @param api 翻译源
|
||||||
|
* @param sourceString 原始文本
|
||||||
|
* @return 翻译结果
|
||||||
|
* @throws Exception 翻译出错
|
||||||
|
*/
|
||||||
|
String translate(TranslateApi api, String sourceString) throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取延迟翻译对象
|
||||||
|
*
|
||||||
|
* @param api 翻译源
|
||||||
|
* @param word 待翻译文本对象
|
||||||
|
* @return 延迟翻译对象
|
||||||
|
*/
|
||||||
|
DelayQueue<DelayWord> getDelayQueue(TranslateApi api, List<WordItem> word);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重设延迟时间
|
||||||
|
*
|
||||||
|
* @param api 翻译接口
|
||||||
|
* @param index 序列号
|
||||||
|
* @param delayWord 延迟对象
|
||||||
|
*/
|
||||||
|
void resetDelayTime(TranslateApi api, int index, DelayWord delayWord);
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package cn.octopusyan.dmt.translate.factory;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.translate.DelayWord;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
import cn.octopusyan.dmt.translate.processor.AbstractTranslateProcessor;
|
||||||
|
import cn.octopusyan.dmt.translate.processor.BaiduTranslateProcessor;
|
||||||
|
import cn.octopusyan.dmt.translate.processor.FreeBaiduTranslateProcessor;
|
||||||
|
import cn.octopusyan.dmt.translate.processor.FreeGoogleTranslateProcessor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.DelayQueue;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译处理器
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class TranslateFactoryImpl implements TranslateFactory {
|
||||||
|
private static TranslateFactoryImpl impl;
|
||||||
|
private final Map<String, AbstractTranslateProcessor> processorMap = new HashMap<>();
|
||||||
|
private final List<AbstractTranslateProcessor> processorList = new ArrayList<>();
|
||||||
|
|
||||||
|
private TranslateFactoryImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized TranslateFactoryImpl getInstance() {
|
||||||
|
if (impl == null) {
|
||||||
|
impl = new TranslateFactoryImpl();
|
||||||
|
impl.initProcessor();
|
||||||
|
}
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initProcessor() {
|
||||||
|
processorList.addAll(Arrays.asList(
|
||||||
|
new FreeGoogleTranslateProcessor(),
|
||||||
|
new FreeBaiduTranslateProcessor(),
|
||||||
|
new BaiduTranslateProcessor()
|
||||||
|
));
|
||||||
|
for (AbstractTranslateProcessor processor : processorList) {
|
||||||
|
processorMap.put(processor.getSource(), processor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractTranslateProcessor getProcessor(TranslateApi api) {
|
||||||
|
return processorMap.get(api.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DelayQueue<DelayWord> getDelayQueue(TranslateApi api, List<WordItem> words) {
|
||||||
|
var queue = new DelayQueue<DelayWord>();
|
||||||
|
|
||||||
|
// 设置翻译延迟
|
||||||
|
AbstractTranslateProcessor processor = getProcessor(api);
|
||||||
|
for (int i = 0; i < words.size(); i++) {
|
||||||
|
// 翻译对象
|
||||||
|
DelayWord delayWord = new DelayWord(words.get(i));
|
||||||
|
|
||||||
|
// 设置翻译源
|
||||||
|
delayWord.setApi(api);
|
||||||
|
long time = 1000L / processor.qps();
|
||||||
|
delayWord.setDelayTime(time * (i + 1), TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
queue.add(delayWord);
|
||||||
|
}
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetDelayTime(TranslateApi api, int index, DelayWord delayWord) {
|
||||||
|
AbstractTranslateProcessor processor = getProcessor(api);
|
||||||
|
// 设置翻译源
|
||||||
|
delayWord.setApi(api);
|
||||||
|
long time = 1000L / processor.qps();
|
||||||
|
delayWord.setDelayTime(time * (index + 1), TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译(英->中)
|
||||||
|
* <p> TODO 切换语种
|
||||||
|
*
|
||||||
|
* @param api 翻译源
|
||||||
|
* @param sourceString 原始文本
|
||||||
|
* @return 翻译结果
|
||||||
|
* @throws Exception 翻译出错
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String translate(TranslateApi api, String sourceString) throws Exception {
|
||||||
|
return getProcessor(api).translate(sourceString);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package cn.octopusyan.dmt.translate.processor;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.dmt.translate.ApiKey;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译处理器抽象类
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public abstract class AbstractTranslateProcessor implements TranslateProcessor {
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
protected static final HttpUtil httpUtil = HttpUtil.getInstance();
|
||||||
|
protected final TranslateApi translateApi;
|
||||||
|
|
||||||
|
public AbstractTranslateProcessor(TranslateApi translateApi) {
|
||||||
|
this.translateApi = translateApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return translateApi.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TranslateApi source() {
|
||||||
|
return translateApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean needApiKey() {
|
||||||
|
return source().needApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean configuredKey() {
|
||||||
|
return ConfigManager.hasTranslateApiKey(source());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int qps() {
|
||||||
|
return ConfigManager.translateQps(source());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Api配置信息
|
||||||
|
*/
|
||||||
|
protected ApiKey getApiKey() {
|
||||||
|
if (!configuredKey()) {
|
||||||
|
String message = String.format("未配置【%s】翻译源认证信息!", source().getLabel());
|
||||||
|
logger.error(message);
|
||||||
|
throw new RuntimeException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
String appid = ConfigManager.translateAppid(source());
|
||||||
|
String apikey = ConfigManager.translateApikey(source());
|
||||||
|
return new ApiKey(appid, apikey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String translate(String original) throws Exception {
|
||||||
|
|
||||||
|
if (needApiKey() && !configuredKey()) {
|
||||||
|
String message = String.format("未配置【%s】翻译源认证信息!", source().getLabel());
|
||||||
|
logger.error(message);
|
||||||
|
throw new RuntimeException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return customTranslate(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译处理
|
||||||
|
*
|
||||||
|
* @param source 原始文本
|
||||||
|
* @return 翻译结果
|
||||||
|
*/
|
||||||
|
public abstract String customTranslate(String source) throws Exception;
|
||||||
|
}
|
@ -1,13 +1,15 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
package cn.octopusyan.dmt.translate.processor;
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.ApiKey;
|
import cn.octopusyan.dmt.common.util.JsonUtil;
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
import cn.octopusyan.dmt.translate.ApiKey;
|
||||||
import com.alibaba.fastjson2.JSON;
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,8 +21,8 @@ public class BaiduTranslateProcessor extends AbstractTranslateProcessor {
|
|||||||
|
|
||||||
private ApiKey apiKey;
|
private ApiKey apiKey;
|
||||||
|
|
||||||
public BaiduTranslateProcessor(TranslateSource translateSource) {
|
public BaiduTranslateProcessor() {
|
||||||
super(translateSource);
|
super(TranslateApi.BAIDU);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -37,10 +39,10 @@ public class BaiduTranslateProcessor extends AbstractTranslateProcessor {
|
|||||||
@Override
|
@Override
|
||||||
public String customTranslate(String source) throws IOException, InterruptedException {
|
public String customTranslate(String source) throws IOException, InterruptedException {
|
||||||
apiKey = getApiKey();
|
apiKey = getApiKey();
|
||||||
String appid = apiKey.getAppid();
|
String appid = apiKey.appid();
|
||||||
String salt = UUID.randomUUID().toString().replace("-", "");
|
String salt = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
|
||||||
JSONObject param = new JSONObject();
|
Map<String, Object> param = new HashMap<>();
|
||||||
param.put("q", source);
|
param.put("q", source);
|
||||||
param.put("from", "auto");
|
param.put("from", "auto");
|
||||||
param.put("to", "zh");
|
param.put("to", "zh");
|
||||||
@ -48,20 +50,20 @@ public class BaiduTranslateProcessor extends AbstractTranslateProcessor {
|
|||||||
param.put("salt", salt);
|
param.put("salt", salt);
|
||||||
param.put("sign", getSign(appid, source, salt));
|
param.put("sign", getSign(appid, source, salt));
|
||||||
|
|
||||||
String resp = httpUtil.get(url(), null, param);
|
String resp = httpUtil.get(url(), null, JsonUtil.parseJsonObject(param));
|
||||||
JSONObject jsonObject = JSON.parseObject(resp);
|
JsonNode json = JsonUtil.parseJsonObject(resp);
|
||||||
|
|
||||||
if (!jsonObject.containsKey("trans_result")) {
|
if (!json.has("trans_result")) {
|
||||||
Object errorMsg = jsonObject.get("error_msg");
|
Object errorMsg = json.get("error_msg");
|
||||||
logger.error("翻译失败: {}", errorMsg);
|
logger.error("翻译失败: {}", errorMsg);
|
||||||
throw new RuntimeException("翻译失败: " + errorMsg);
|
throw new RuntimeException(String.valueOf(errorMsg));
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonObject.getJSONArray("trans_result").getJSONObject(0).getString("dst");
|
return json.get("trans_result").get(0).get("dst").asText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSign(String appid, String q, String salt) {
|
private String getSign(String appid, String q, String salt) {
|
||||||
return encrypt2ToMD5(appid + q + salt + apiKey.getApiKey());
|
return encrypt2ToMD5(appid + q + salt + apiKey.apiKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -0,0 +1,107 @@
|
|||||||
|
package cn.octopusyan.dmt.translate.processor;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.manager.http.CookieManager;
|
||||||
|
import cn.octopusyan.dmt.common.util.JsonUtil;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpCookie;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 谷歌 免费翻译接口
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
|
||||||
|
private static final Map<String, Object> header = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
header.put("Origin", "https://fanyi.baidu.com");
|
||||||
|
header.put("Referer", "https://fanyi.baidu.com");
|
||||||
|
header.put("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
|
||||||
|
header.put("Accept","*/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public FreeBaiduTranslateProcessor() {
|
||||||
|
super(TranslateApi.FREE_BAIDU);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String url() {
|
||||||
|
return "https://fanyi.baidu.com/transapi";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int qps() {
|
||||||
|
return source().getDefaultQps();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译处理
|
||||||
|
*
|
||||||
|
* @param source 待翻译单词
|
||||||
|
* @return 翻译结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String customTranslate(String source) throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
Map<String, Object> param = new HashMap<>();
|
||||||
|
param.put("query", source);
|
||||||
|
param.put("from", "auto");
|
||||||
|
param.put("to", "zh");
|
||||||
|
param.put("source", "txt");
|
||||||
|
|
||||||
|
String resp = httpUtil.post(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(param));
|
||||||
|
JsonNode json = JsonUtil.parseJsonObject(resp);
|
||||||
|
|
||||||
|
if (!json.has("data")) {
|
||||||
|
String errorMsg = json.get("errmsg").asText();
|
||||||
|
if("访问出现异常,请刷新后重试!".equals(errorMsg) && !header.containsKey("Cookie")) {
|
||||||
|
checkCookie();
|
||||||
|
return customTranslate(source);
|
||||||
|
}
|
||||||
|
throw new RuntimeException(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.get("data").get(0).get("dst").asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCookie() throws IOException, InterruptedException {
|
||||||
|
// 短时大量请求会被ban,需要添加验证cookie
|
||||||
|
|
||||||
|
if (header.containsKey("Cookie")) return;
|
||||||
|
|
||||||
|
List<HttpCookie> cookieList = CookieManager.getStore().get(URI.create("https://baidu.com"));
|
||||||
|
boolean noneMatch = cookieList.stream()
|
||||||
|
.filter(cookie -> "ab_sr".equals(cookie.getName()) || "BAIDUID".equals(cookie.getName()))
|
||||||
|
.count() < 2;
|
||||||
|
|
||||||
|
if (noneMatch) {
|
||||||
|
String url = "https://miao.baidu.com/abdr?_o=https%3A%2F%2Ffanyi.baidu.com";
|
||||||
|
Map<String, Object> param = new HashMap<>();
|
||||||
|
param.put("data", miao_data);
|
||||||
|
param.put("key_id", "6e75c85adea0454a");
|
||||||
|
param.put("enc", 2);
|
||||||
|
httpUtil.post(url, JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(param));
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieList = CookieManager.getStore().get(URI.create("https://baidu.com"));
|
||||||
|
List<HttpCookie> cookies = cookieList.stream()
|
||||||
|
.filter(cookie -> "ab_sr".equals(cookie.getName()) || "BAIDUID".equals(cookie.getName()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
String collect = cookies.stream()
|
||||||
|
.map(cookie -> cookie.getName() + "=" + cookie.getValue())
|
||||||
|
.collect(Collectors.joining(";"));
|
||||||
|
|
||||||
|
header.put("Cookie", collect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String miao_data = "+wPJcl395nhVS+Fg/zTBIkBognAAK5IQi6wkGt3z1jk1jMuJOn+IkhaL1M7GHYxq/0V6+zKXaEVRs77WDfP/fDS0suNzgl3NdbhGSoHJEJk4fVMeU0pZxrgFq7Bzch6Aehw7MD6WlUDfyHWMarBN9OxH1UiW6Dn38uUkBjpsTDZYeNWOlmwy8YKq3FlRZiWzGYaP7K3DfBwWbT059D0KT2/mGUrHVgPVAkU572qNLzTxFyFneYTmPDQC0j+fl6+kxhA460WIG5aL19v4Gx+T7YJwIGe07QdhlQuy8Q6bpAIb8lj1SGjX9kS8P8GtI7TQv7gOlZ8ShUgV7941JXbIsJZgoWHFNq5Xge6XcVOUJsSQzAKsAgIlhgnlfC9IBAGK85KzeDC7JoBi4t0BXKgKUjYM9Veh7cA3yfudWKjSpyN6mv97apGR4Vl+8wbugPSCQRQlxPDunGUTGionf4hKxNZJYz62dwm0E/pP21PGPwnihDi7mQNxh40aE7IG0kORudLChvrcj85n4U/Rxkoq3TXTnSK1YAc4Pi/dIuR5HSUZyUBEqlvldU3jXqgUlfqeqjkn5Ug9gS6IqYEHE6FZdAGw/3y247pojWs2L56RSrUc6LoyQ7P9QNPOBr/v9HngHLRnFovrrZ57KmMl2hCiX5r1Q473CyjBhTjix3gQIFCwmM6rmEPIIU+cr0lvQrdhjxsiETk2sd17dR9gR2EjbNjeTby9QyHJTima5T6lk0K0orADrF2lNA/Xo2Iv/cMFXkc1ss7a19UQTdMLmiKgWdllWxH8Yp8B5o4KrLh+pMCGy6NeFn6FuWeAQgY8EB1r9H3PuenKaQ02VckkUPN1h1szjDI7otIFHYnQFJrp4vy4aM8wwcQS/+R2Aw0FwA5e2IWcZSwoTIc1i9YIeKgUpiConxhqZeKIhUEz33qExW0IS9mMs558JbAYsUG8KcMFtyqVo2sgtXteMAXMB9yX5/huZMR9HOO0v8x4KCZ+hwZczoVe3AmmUZLq1+rY9ngCp5D/3CcElcH3SU9HmlVXZ2VvLpa9wGIRP1UAHFuTxwVLvxJP07fbR31rLkoC22DbemRshBnv4EbIP/mkO8l/P1Y6RGEkS3b7b2MoBIWh4atrqOmUCG6w1KwESSi4Y5VKpdEkU/gL4ea7kr/GRJVWomSKSDqUEuloUI4hfLpmJs4h1JZTotOBcVjarOIW5d3j2Gm6R8X6vk24sUukOR1QOzP1gkoUaOYR+6Jcl+20IQZtIFMwFeMYtKrB5HquwYgHmUvO5hdxuFRwXosegVJmo+9MxkYjHTJbAgqZfKMc8kRjr64YBhwE30P+KLw+TafbgrA3trZQCyN3u5O5/2rk3GJmbhcDhZlYrYepj4Rymw8FB6LF0PkZZzRtWtQWv2gnMZuLmo7i5flL/MbiUMXKnsy3+Z6HHKNOqUzGUOZx84xizLkxwVMzGHrtSOub4wIhSWr7/z9GNa9qJuNJYsPXIE8wn245mHaheCm+MCxjIJMKdb72PBbQFF517kV+BxsNrxbFGe6ffJ/03KhXg0jJ1lAQc07obvOkdGVRr8DHTiS3IBfN7ofZMjVhXXpH2kUtepLfWvQf053yH2VQIEq62uxZP+RfpmdiBePEXQRBk5EA+178YbyavPE6yeu8zlub6GGlvuvrtnQmPUyvmO1hebiQK8B/V5TXxB+7IhPQ8okEll6bldVCoIxkbyAIBTK7rrDhD53HJ9capFgh/Q0XHMchYSYielT+Et8UIqUmVO94K5yl/nQhO1Vc2FsHkp2JWDDMQTuZyfmv+FS5OTeu39UznwXD36kyr2HvBEVoCMBqJrhI9WnEng53aEtcWQk4ewS+HllIoBgeJgexfs3MQK0mMP+6qV/idvB3S8Kzt/+SZsA0cHGvPvQzdePCRGNgCFOHPcpHPW/Yz4bwo4dzM64CYnL17Z4/fwcodMBG+KqG6ZyXe7EBby5RUE5OnhAGdR8T6WlIjNlU5TAIPbZ32i5nugWAcYJWJKsuITjAl4sRYY6Tpj19yIdHX2CDnU/O4arL8ijniydgcIEyYJbSCFmeJwyIv+ZSrB3RW0fYuAs4Q3AbeSK5TA9EICpB2w22kgditKO+uvrxw2LzGGND5C684YZq+XGmocAfpcte6+1PFs4NwuN41lOxDry8dwXr2mUIugcpnzsoF5Wgwyen0ISb2qaXODTvd/T5HEJcmpZdUJ1c+g+nEPi2OyX+MBXR";
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
package cn.octopusyan.dmt.translate.processor;
|
||||||
|
|
||||||
import cn.octopusyan.dayzmodtranslator.manager.translate.TranslateSource;
|
|
||||||
import com.alibaba.fastjson2.JSONArray;
|
import cn.octopusyan.dmt.common.util.JsonUtil;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import cn.octopusyan.dmt.translate.TranslateApi;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 谷歌 免费翻译接口
|
* 谷歌 免费翻译接口
|
||||||
@ -13,8 +16,8 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
|
public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
|
||||||
|
|
||||||
public FreeGoogleTranslateProcessor(TranslateSource translateSource) {
|
public FreeGoogleTranslateProcessor() {
|
||||||
super(translateSource);
|
super(TranslateApi.FREE_GOOGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -22,6 +25,11 @@ public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
|
|||||||
return "https://translate.googleapis.com/translate_a/single";
|
return "https://translate.googleapis.com/translate_a/single";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int qps() {
|
||||||
|
return source().getDefaultQps();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译处理
|
* 翻译处理
|
||||||
*
|
*
|
||||||
@ -31,21 +39,22 @@ public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
|
|||||||
@Override
|
@Override
|
||||||
public String customTranslate(String source) throws IOException, InterruptedException {
|
public String customTranslate(String source) throws IOException, InterruptedException {
|
||||||
|
|
||||||
JSONObject form = new JSONObject();
|
Map<String, Object> form = new HashMap<>();
|
||||||
form.put("client", "gtx");
|
form.put("client", "gtx");
|
||||||
form.put("dt", "t");
|
form.put("dt", "t");
|
||||||
form.put("sl", "auto");
|
form.put("sl", "auto");
|
||||||
form.put("tl", "zh-CN");
|
form.put("tl", "zh-CN");
|
||||||
form.put("q", source);
|
form.put("q", source);
|
||||||
|
|
||||||
JSONObject header = new JSONObject();
|
Map<String, Object> header = new HashMap<>();
|
||||||
StringBuilder retStr = new StringBuilder();
|
StringBuilder retStr = new StringBuilder();
|
||||||
// TODO 短时大量请求会被ban,需要浏览器验证添加cookie
|
// TODO 短时大量请求会被ban,需要浏览器验证添加cookie
|
||||||
String resp = httpUtil.get(url(), header, form);
|
|
||||||
JSONArray jsonObject = JSONArray.parseArray(resp);
|
String resp = httpUtil.get(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(form));
|
||||||
for (Object o : jsonObject.getJSONArray(0)) {
|
JsonNode json = JsonUtil.parseJsonObject(resp);
|
||||||
JSONArray a = (JSONArray) o;
|
|
||||||
retStr.append(a.getString(0));
|
for (JsonNode o : json.get(0)) {
|
||||||
|
retStr.append(o.get(0).asText());
|
||||||
}
|
}
|
||||||
|
|
||||||
return retStr.toString();
|
return retStr.toString();
|
@ -1,4 +1,4 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
package cn.octopusyan.dmt.translate.processor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译处理器
|
* 翻译处理器
|
||||||
@ -6,6 +6,7 @@ package cn.octopusyan.dayzmodtranslator.manager.translate.processor;
|
|||||||
* @author octopus_yan@foxmail.com
|
* @author octopus_yan@foxmail.com
|
||||||
*/
|
*/
|
||||||
public interface TranslateProcessor {
|
public interface TranslateProcessor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译源 api接口地址
|
* 翻译源 api接口地址
|
||||||
*/
|
*/
|
||||||
@ -29,9 +30,9 @@ public interface TranslateProcessor {
|
|||||||
/**
|
/**
|
||||||
* 翻译
|
* 翻译
|
||||||
*
|
*
|
||||||
* @param source 原始文本
|
* @param original 原始文本
|
||||||
* @return 翻译结果
|
* @return 翻译结果
|
||||||
* @throws Exception 翻译出错
|
* @throws Exception 翻译出错
|
||||||
*/
|
*/
|
||||||
String translate(String source) throws Exception;
|
String translate(String original) throws Exception;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package cn.octopusyan.dayzmodtranslator.util;
|
package cn.octopusyan.dmt.utils;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.io.filefilter.CanReadFileFilter;
|
import org.apache.commons.io.filefilter.CanReadFileFilter;
|
||||||
@ -6,11 +6,9 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -96,8 +94,24 @@ public class FileUtil {
|
|||||||
return fileName.substring(0, fileName.lastIndexOf("."));
|
return fileName.substring(0, fileName.lastIndexOf("."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getMimeType(Path path) {
|
||||||
|
try {
|
||||||
|
return Files.probeContentType(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection<File> listFile(File file) {
|
||||||
|
return FileUtils.listFiles(file, CanReadFileFilter.CAN_READ, null);
|
||||||
|
}
|
||||||
|
|
||||||
public static List<String> listFileNames(String path) {
|
public static List<String> listFileNames(String path) {
|
||||||
Collection<File> files = FileUtils.listFiles(new File(path), CanReadFileFilter.CAN_READ, null);
|
return listFileNames(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> listFileNames(File file) {
|
||||||
|
Collection<File> files = listFile(file);
|
||||||
return files.stream().map(File::getName).collect(Collectors.toList());
|
return files.stream().map(File::getName).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,4 +146,36 @@ public class FileUtil {
|
|||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断文件的编码格式
|
||||||
|
*
|
||||||
|
* @return 文件编码格式
|
||||||
|
*/
|
||||||
|
public static String getCharsets(File file) {
|
||||||
|
try (InputStream in = new FileInputStream(file)) {
|
||||||
|
int p = (in.read() << 8) + in.read();
|
||||||
|
String code = "GBK";
|
||||||
|
|
||||||
|
switch (p) {
|
||||||
|
case 59524:
|
||||||
|
code = "UTF-8";
|
||||||
|
break;
|
||||||
|
case 0xfffe:
|
||||||
|
code = "Unicode";
|
||||||
|
break;
|
||||||
|
case 0xfeff:
|
||||||
|
code = "UTF-16BE";
|
||||||
|
break;
|
||||||
|
case 48581:
|
||||||
|
code = "GBK";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
407
src/main/java/cn/octopusyan/dmt/utils/PBOUtil.java
Normal file
407
src/main/java/cn/octopusyan/dmt/utils/PBOUtil.java
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
package cn.octopusyan.dmt.utils;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.config.Constants;
|
||||||
|
import cn.octopusyan.dmt.common.util.ProcessesUtil;
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.view.ConsoleLog;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.LineIterator;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PBO 文件工具
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class PBOUtil {
|
||||||
|
public static final ConsoleLog consoleLog = ConsoleLog.getInstance(PBOUtil.class);
|
||||||
|
|
||||||
|
private static final ProcessesUtil processesUtil = ProcessesUtil.init(Constants.BIN_DIR_PATH);
|
||||||
|
|
||||||
|
private static final String UNPACK_COMMAND = STR."\{Constants.PBOC_FILE} unpack -o \{Constants.TMP_DIR_PATH} %s";
|
||||||
|
private static final String PACK_COMMAND = STR."\{Constants.PBOC_FILE} pack -o %s %s";
|
||||||
|
private static final String CFG_COMMAND = STR."\{Constants.CFG_CONVERT_FILE} %s -dst %s %s";
|
||||||
|
|
||||||
|
private static final String FILE_NAME_STRING_TABLE = "stringtable.csv";
|
||||||
|
private static final String FILE_NAME_CONFIG_BIN = "config.bin";
|
||||||
|
private static final String FILE_NAME_CONFIG_CPP = "config.cpp";
|
||||||
|
private static final String[] FILE_NAME_LIST = new String[]{"csv", "bin", "cpp", "layout"};
|
||||||
|
|
||||||
|
private static final Pattern CPP_PATTERN = Pattern.compile(".*(displayName|descriptionShort) ?= ?\"(.*)\";.*");
|
||||||
|
private static final Pattern LAYOUT_PATTERN = Pattern.compile(".*text \"(.*)\".*");
|
||||||
|
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
|
||||||
|
String srcFilePath = Objects.requireNonNull(PBOUtil.class.getResource("/bin")).getPath();
|
||||||
|
try {
|
||||||
|
File destDir = new File(Constants.BIN_DIR_PATH);
|
||||||
|
FileUtils.forceMkdir(destDir);
|
||||||
|
FileUtils.copyDirectory(new File(srcFilePath), destDir);
|
||||||
|
} catch (IOException e) {
|
||||||
|
consoleLog.error("Util 初始化失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包pbo文件
|
||||||
|
*
|
||||||
|
* @param path pbo文件地址
|
||||||
|
* @return 解包输出路径
|
||||||
|
*/
|
||||||
|
public static String unpack(String path) {
|
||||||
|
return unpack(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包pbo文件
|
||||||
|
*
|
||||||
|
* @param pboFile pbo文件
|
||||||
|
* @return 解包输出路径
|
||||||
|
*/
|
||||||
|
public static String unpack(File pboFile) {
|
||||||
|
if (!pboFile.exists())
|
||||||
|
throw new RuntimeException("文件不存在!");
|
||||||
|
|
||||||
|
File directory = new File(Constants.TMP_DIR_PATH);
|
||||||
|
String outputPath = Constants.TMP_DIR_PATH + File.separator + FileUtil.mainName(pboFile);
|
||||||
|
try {
|
||||||
|
FileUtils.deleteQuietly(new File(outputPath));
|
||||||
|
FileUtils.forceMkdir(directory);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("文件夹创建失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
String command = String.format(UNPACK_COMMAND, pboFile.getAbsolutePath());
|
||||||
|
consoleLog.debug(STR."unpack command ==> [\{command}]");
|
||||||
|
boolean exec = processesUtil.exec(command);
|
||||||
|
if (!exec)
|
||||||
|
throw new RuntimeException("解包失败!");
|
||||||
|
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包pbo文件
|
||||||
|
*
|
||||||
|
* @param unpackPath pbo解包文件路径
|
||||||
|
* @return 打包文件
|
||||||
|
*/
|
||||||
|
public static File pack(String unpackPath) {
|
||||||
|
String outputPath = STR."\{unpackPath}.pbo";
|
||||||
|
|
||||||
|
// 打包文件临时保存路径
|
||||||
|
File packFile = new File(outputPath);
|
||||||
|
if (packFile.exists()) {
|
||||||
|
// 如果存在则删除
|
||||||
|
FileUtils.deleteQuietly(packFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
String command = String.format(PACK_COMMAND, Constants.TMP_DIR_PATH, unpackPath);
|
||||||
|
consoleLog.debug(STR."pack command ==> [\{command}]");
|
||||||
|
|
||||||
|
boolean exec = processesUtil.exec(command);
|
||||||
|
if (!exec) throw new RuntimeException("打包失败!");
|
||||||
|
|
||||||
|
return packFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找可翻译文本
|
||||||
|
*
|
||||||
|
* @param path 根目录
|
||||||
|
*/
|
||||||
|
public static List<WordItem> findWord(String path) {
|
||||||
|
return findWord(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<WordItem> findWord(File file) {
|
||||||
|
ArrayList<WordItem> wordItems = new ArrayList<>();
|
||||||
|
if (!file.exists())
|
||||||
|
return wordItems;
|
||||||
|
|
||||||
|
List<File> files = new ArrayList<>(FileUtils.listFiles(file, FILE_NAME_LIST, true));
|
||||||
|
for (File item : files) {
|
||||||
|
wordItems.addAll(findWordByFile(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
return wordItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入文件
|
||||||
|
*
|
||||||
|
* @param wordFileMap 文件对应文本map
|
||||||
|
*/
|
||||||
|
public static void writeWords(Map<File, List<WordItem>> wordFileMap) {
|
||||||
|
|
||||||
|
for (Map.Entry<File, List<WordItem>> entry : wordFileMap.entrySet()) {
|
||||||
|
|
||||||
|
Map<Integer, WordItem> wordMap = entry.getValue().stream()
|
||||||
|
.collect(Collectors.toMap(WordItem::getLines, Function.identity()));
|
||||||
|
|
||||||
|
File file = entry.getKey();
|
||||||
|
|
||||||
|
// 需要转bin文件时,写入bak目录下cpp文件
|
||||||
|
boolean hasBin = new File(outFilePath(file, ".bin")).exists();
|
||||||
|
String writePath = file.getAbsolutePath().replace(Constants.BAK_DIR_PATH, Constants.TMP_DIR_PATH);
|
||||||
|
File writeFile = hasBin ? file : new File(writePath);
|
||||||
|
|
||||||
|
AtomicInteger lineIndex = new AtomicInteger(0);
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
|
||||||
|
consoleLog.info("正在写入文件[{}]", writeFile.getAbsolutePath());
|
||||||
|
|
||||||
|
try (LineIterator it = FileUtils.lineIterator(file, StandardCharsets.UTF_8.name())) {
|
||||||
|
while (it.hasNext()) {
|
||||||
|
String line = it.next();
|
||||||
|
WordItem word = wordMap.get(lineIndex.get());
|
||||||
|
|
||||||
|
// 当前行是否有需要替换的文本
|
||||||
|
// TODO 是否替换空文本
|
||||||
|
if (word != null && line.contains(word.getOriginal())) {
|
||||||
|
line = line.substring(0, word.getIndex()) +
|
||||||
|
line.substring(word.getIndex()).replace(word.getOriginal(), word.getChinese());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存行内容
|
||||||
|
lines.add(line);
|
||||||
|
|
||||||
|
lineIndex.addAndGet(1);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]读取出错", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 写入文件
|
||||||
|
String charsets = writeFile.getName().endsWith(".layout") ? FileUtil.getCharsets(writeFile) : StandardCharsets.UTF_8.name();
|
||||||
|
FileUtils.writeLines(writeFile, charsets, lines);
|
||||||
|
} catch (IOException e) {
|
||||||
|
consoleLog.error(STR."文件(\{writeFile.getAbsoluteFile()})写入失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPP转BIN (覆盖TMP下BIN文件)
|
||||||
|
if (hasBin) cpp2bin(writeFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找文件内可翻译文本
|
||||||
|
*
|
||||||
|
* @param file 文件
|
||||||
|
* @return 可翻译文本信息列表
|
||||||
|
*/
|
||||||
|
private static List<WordItem> findWordByFile(File file) {
|
||||||
|
if (!FILE_NAME_CONFIG_CPP.equals(file.getName())
|
||||||
|
&& !FILE_NAME_CONFIG_BIN.equals(file.getName())
|
||||||
|
&& !FILE_NAME_STRING_TABLE.equals(file.getName())
|
||||||
|
&& !file.getName().endsWith(".layout")
|
||||||
|
) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建备份(在bak文件夹下的同级目录
|
||||||
|
file = createBak(file);
|
||||||
|
|
||||||
|
// bin转cpp
|
||||||
|
if (FILE_NAME_CONFIG_BIN.equals(file.getName())) {
|
||||||
|
file = bin2cpp(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
String charset = file.getName().endsWith(".layout") ? FileUtil.getCharsets(file) : StandardCharsets.UTF_8.name();
|
||||||
|
|
||||||
|
try (LineIterator it = FileUtils.lineIterator(file, charset)) {
|
||||||
|
// CPP
|
||||||
|
if (FILE_NAME_CONFIG_CPP.equals(file.getName())) {
|
||||||
|
return findWordByCPP(file, it);
|
||||||
|
}
|
||||||
|
// CSV
|
||||||
|
if (FILE_NAME_STRING_TABLE.equals(file.getName())) {
|
||||||
|
return findWordByCSV(file, it);
|
||||||
|
}
|
||||||
|
// layout
|
||||||
|
if (file.getName().endsWith(".layout")) {
|
||||||
|
return findWordByLayout(file, it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 待添加更多文件格式
|
||||||
|
return Collections.emptyList();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
consoleLog.error(STR."文件[\{file.getAbsoluteFile()}]读取出错", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从csv文件中读取可翻译文本
|
||||||
|
*
|
||||||
|
* @param file csv文件
|
||||||
|
* @param it 行内容遍历器
|
||||||
|
* @return 可翻译文本列表
|
||||||
|
*/
|
||||||
|
private static List<WordItem> findWordByCSV(File file, LineIterator it) {
|
||||||
|
ArrayList<WordItem> wordItems = new ArrayList<>();
|
||||||
|
AtomicInteger lines = new AtomicInteger(0);
|
||||||
|
int index = -1;
|
||||||
|
String line;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
line = it.next();
|
||||||
|
List<String> split = Arrays.stream(line.split(",")).toList();
|
||||||
|
|
||||||
|
if (lines.get() == 0) {
|
||||||
|
index = split.indexOf("\"chinese\"");
|
||||||
|
} else if (index < split.size()) {
|
||||||
|
// 原文
|
||||||
|
String original = split.get(index).replaceAll("\"", "");
|
||||||
|
// 开始下标
|
||||||
|
Integer startIndex = line.indexOf(original);
|
||||||
|
// 添加单词
|
||||||
|
if (original.length() > 1) {
|
||||||
|
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.addAndGet(1);
|
||||||
|
}
|
||||||
|
return wordItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从layout文件中读取可翻译文本
|
||||||
|
*
|
||||||
|
* @param file layout文件
|
||||||
|
* @param it 行内容遍历器
|
||||||
|
* @return 可翻译文本列表
|
||||||
|
*/
|
||||||
|
private static List<WordItem> findWordByLayout(File file, LineIterator it) {
|
||||||
|
ArrayList<WordItem> wordItems = new ArrayList<>();
|
||||||
|
AtomicInteger lines = new AtomicInteger(0);
|
||||||
|
String line;
|
||||||
|
Matcher matcher;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
line = it.next();
|
||||||
|
matcher = LAYOUT_PATTERN.matcher(line);
|
||||||
|
if (StringUtils.isNoneEmpty(line) && matcher.matches()) {
|
||||||
|
// 原文
|
||||||
|
String original = matcher.group(1);
|
||||||
|
// 开始下标
|
||||||
|
Integer startIndex = line.indexOf(original);
|
||||||
|
// 添加单词
|
||||||
|
if (original.length() > 1) {
|
||||||
|
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.addAndGet(1);
|
||||||
|
}
|
||||||
|
return wordItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取cpp文件内可翻译文本
|
||||||
|
*
|
||||||
|
* @param file cpp文件
|
||||||
|
* @param it 行内容遍历器
|
||||||
|
* @return 可翻译文本列表
|
||||||
|
*/
|
||||||
|
private static List<WordItem> findWordByCPP(File file, LineIterator it) {
|
||||||
|
ArrayList<WordItem> wordItems = new ArrayList<>();
|
||||||
|
AtomicInteger lines = new AtomicInteger(0);
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
String line = it.next();
|
||||||
|
|
||||||
|
Matcher matcher = CPP_PATTERN.matcher(line);
|
||||||
|
if (!line.contains("$") && matcher.matches()) {
|
||||||
|
|
||||||
|
String name = matcher.group(1);
|
||||||
|
|
||||||
|
// 原始文本
|
||||||
|
int startIndex = line.indexOf(name) + name.length();
|
||||||
|
String original = matcher.group(2);
|
||||||
|
|
||||||
|
// 添加单词
|
||||||
|
if (original.length() > 1) {
|
||||||
|
wordItems.add(new WordItem(file, lines.get(), startIndex, original, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.addAndGet(1);
|
||||||
|
}
|
||||||
|
return wordItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建备份文件
|
||||||
|
*/
|
||||||
|
private static File createBak(File file) {
|
||||||
|
try {
|
||||||
|
String absolutePath = file.getAbsolutePath().replace(Constants.TMP_DIR_PATH, Constants.BAK_DIR_PATH);
|
||||||
|
File destFile = new File(absolutePath);
|
||||||
|
FileUtils.copyFile(file, destFile);
|
||||||
|
return destFile;
|
||||||
|
} catch (IOException e) {
|
||||||
|
consoleLog.error(STR."创建备份文件失败[\{file.getAbsolutePath()}]", e);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bin 转 cpp
|
||||||
|
*
|
||||||
|
* @param file bin文件
|
||||||
|
* @return cpp文件
|
||||||
|
*/
|
||||||
|
private static File bin2cpp(File file) {
|
||||||
|
boolean exec = processesUtil.exec(toTxtCommand(file));
|
||||||
|
|
||||||
|
if (!exec) throw new RuntimeException("bin2cpp 失败");
|
||||||
|
|
||||||
|
return new File(outFilePath(file, ".cpp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cpp 转 bin
|
||||||
|
*
|
||||||
|
* @param file bin文件
|
||||||
|
*/
|
||||||
|
private static void cpp2bin(File file) {
|
||||||
|
boolean exec = processesUtil.exec(toBinCommand(file));
|
||||||
|
|
||||||
|
if (!exec) throw new RuntimeException("cpp2bin 失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cpp to bin 命令
|
||||||
|
*/
|
||||||
|
private static String toBinCommand(File cppFile) {
|
||||||
|
String outFilePath = outFilePath(cppFile, ".bin");
|
||||||
|
outFilePath = outFilePath.replace(Constants.BAK_DIR_PATH, Constants.TMP_DIR_PATH);
|
||||||
|
return String.format(CFG_COMMAND, "-bin", outFilePath, cppFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bin to cpp 命令
|
||||||
|
*/
|
||||||
|
private static String toTxtCommand(File binFile) {
|
||||||
|
String outFilePath = outFilePath(binFile, ".cpp");
|
||||||
|
return String.format(CFG_COMMAND, "-txt", outFilePath, binFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String outFilePath(File file, String suffix) {
|
||||||
|
return file.getParentFile().getAbsolutePath() + File.separator + FileUtil.mainName(file) + suffix;
|
||||||
|
}
|
||||||
|
}
|
43
src/main/java/cn/octopusyan/dmt/utils/Resources.java
Normal file
43
src/main/java/cn/octopusyan/dmt/utils/Resources.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
package cn.octopusyan.dmt.utils;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.AppLauncher;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
public final class Resources {
|
||||||
|
|
||||||
|
public static final String MODULE_DIR = "/";
|
||||||
|
|
||||||
|
public static InputStream getResourceAsStream(String resource) {
|
||||||
|
String path = resolve(resource);
|
||||||
|
return Objects.requireNonNull(
|
||||||
|
AppLauncher.class.getResourceAsStream(resolve(path)),
|
||||||
|
"Resource not found: " + path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static URI getResource(String resource) {
|
||||||
|
String path = resolve(resource);
|
||||||
|
URL url = Objects.requireNonNull(AppLauncher.class.getResource(resolve(path)), "Resource not found: " + path);
|
||||||
|
return URI.create(url.toExternalForm());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolve(String resource) {
|
||||||
|
Objects.requireNonNull(resource);
|
||||||
|
return resource.startsWith("/") ? resource : MODULE_DIR + resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPropertyOrEnv(String propertyKey, String envKey) {
|
||||||
|
return System.getProperty(propertyKey, System.getenv(envKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Preferences getPreferences() {
|
||||||
|
return Preferences.userRoot().node("atlantafx");
|
||||||
|
}
|
||||||
|
}
|
151
src/main/java/cn/octopusyan/dmt/view/ConsoleLog.java
Normal file
151
src/main/java/cn/octopusyan/dmt/view/ConsoleLog.java
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package cn.octopusyan.dmt.view;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟控制台输出
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class ConsoleLog {
|
||||||
|
public static final String format = "yyyy/MM/dd hh:mm:ss";
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ConsoleLog.class);
|
||||||
|
private static Logger markerLog;
|
||||||
|
private static TextArea logArea;
|
||||||
|
private final String tag;
|
||||||
|
@Setter
|
||||||
|
private static boolean showDebug = false;
|
||||||
|
|
||||||
|
public static void init(TextArea logArea) {
|
||||||
|
ConsoleLog.logArea = logArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConsoleLog(String tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ConsoleLog getInstance(Class<T> clazz) {
|
||||||
|
markerLog = LoggerFactory.getLogger(clazz);
|
||||||
|
return getInstance(clazz.getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConsoleLog getInstance(String tag) {
|
||||||
|
return new ConsoleLog(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isInit() {
|
||||||
|
return log != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void info(String message, Object... param) {
|
||||||
|
printLog(tag, Level.INFO, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void warning(String message, Object... param) {
|
||||||
|
printLog(tag, Level.WARN, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug(String message, Object... param) {
|
||||||
|
if (!showDebug) return;
|
||||||
|
printLog(tag, Level.DEBUG, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void error(String message, Object... param) {
|
||||||
|
printLog(tag, Level.ERROR, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void error(String message, Throwable throwable) {
|
||||||
|
markerLog.error(message, throwable);
|
||||||
|
message = STR."\{message} \{throwable.getMessage()}";
|
||||||
|
printLog(tag, Level.ERROR, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void msg(String message, Object... params) {
|
||||||
|
if (StringUtils.isEmpty(message) || !isInit()) return;
|
||||||
|
message = format(message, params);
|
||||||
|
message = resetConsoleColor(message);
|
||||||
|
|
||||||
|
print(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public void printLog(String tag, Level level, String message, Object... params) {
|
||||||
|
if (!isInit()) return;
|
||||||
|
|
||||||
|
// 时间
|
||||||
|
String time = LocalDateTime.now().format(formatter);
|
||||||
|
// 级别
|
||||||
|
String levelStr = level.code;
|
||||||
|
// 消息
|
||||||
|
message = format(message, params);
|
||||||
|
|
||||||
|
// 拼接后输出
|
||||||
|
String input = STR."\{time} \{levelStr} [\{tag}] - \{message.replace(tag, "")}";
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case WARN -> markerLog.warn(message);
|
||||||
|
case DEBUG -> markerLog.debug(message);
|
||||||
|
// case ERROR -> markerLog.error(message);
|
||||||
|
default -> markerLog.info(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
print(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void print(String message) {
|
||||||
|
var msg = message + (message.endsWith("\n") ? "" : "\n");
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
ConsoleLog.logArea.appendText(msg);
|
||||||
|
// 滚动到底部
|
||||||
|
ConsoleLog.logArea.setScrollTop(Double.MAX_VALUE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================={ 私有方法 }===================================================
|
||||||
|
|
||||||
|
private static String format(String msg, Object... params) {
|
||||||
|
int i = 0;
|
||||||
|
while (msg.contains("{}") && params != null) {
|
||||||
|
msg = msg.replaceFirst("\\{}", String.valueOf(params[i++]).replace("\\", "\\\\"));
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理控制台输出颜色
|
||||||
|
*
|
||||||
|
* @param msg 输出消息
|
||||||
|
* @return 信息
|
||||||
|
*/
|
||||||
|
private static String resetConsoleColor(String msg) {
|
||||||
|
if (!msg.contains("\033[")) return msg;
|
||||||
|
|
||||||
|
return msg.replaceAll("\\033\\[(\\d;)?(\\d+)m", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================{ 枚举 }================================
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Level {
|
||||||
|
INFO("INFO", null),
|
||||||
|
DEBUG("DEBUG", null),
|
||||||
|
WARN("WARN", "-color-danger-emphasis"),
|
||||||
|
ERROR("ERROR", "-color-danger-fg"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String color;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package cn.octopusyan.dmt.view;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.utils.Resources;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
import org.kordamp.ikonli.feather.Feather;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按钮
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class EditButtonTableCell extends TableCell<WordItem, WordItem> {
|
||||||
|
|
||||||
|
public static Callback<TableColumn<WordItem, WordItem>, TableCell<WordItem, WordItem>> forTableColumn(Consumer<WordItem> edit, Consumer<WordItem> translate) {
|
||||||
|
return _ -> new EditButtonTableCell("", edit, translate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Button edit;
|
||||||
|
private final Button translate;
|
||||||
|
|
||||||
|
private static final ImageView translateIcon = new ImageView(new Image(Resources.getResourceAsStream("images/icon/translate.png")));
|
||||||
|
|
||||||
|
static {
|
||||||
|
translateIcon.setFitHeight(20);
|
||||||
|
translateIcon.setFitWidth(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditButtonTableCell(String text, Consumer<WordItem> edit, Consumer<WordItem> translate) {
|
||||||
|
// 编辑
|
||||||
|
this.edit = new Button(text);
|
||||||
|
this.edit.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.FLAT);
|
||||||
|
this.edit.setOnMouseClicked(_ -> {
|
||||||
|
WordItem data = getTableView().getItems().get(getIndex());
|
||||||
|
edit.accept(data);
|
||||||
|
});
|
||||||
|
this.edit.setGraphic(new FontIcon(Feather.EDIT));
|
||||||
|
|
||||||
|
// 翻译
|
||||||
|
ImageView translateIcon = new ImageView(new Image(Resources.getResourceAsStream("images/icon/translate.png")));
|
||||||
|
translateIcon.setFitHeight(20);
|
||||||
|
translateIcon.setFitWidth(20);
|
||||||
|
this.translate = new Button("", translateIcon);
|
||||||
|
this.translate.getStyleClass().addAll(Styles.BUTTON_ICON, Styles.FLAT);
|
||||||
|
this.translate.setOnMouseClicked(_ -> {
|
||||||
|
WordItem data = getTableView().getItems().get(getIndex());
|
||||||
|
translate.accept(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(WordItem item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
if (empty) {
|
||||||
|
setGraphic(null);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* TODO 添加多个操作按钮
|
||||||
|
* setGraphic(Hbox(btn1,btn2));
|
||||||
|
*/
|
||||||
|
setGraphic(new HBox(edit, translate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
src/main/java/cn/octopusyan/dmt/view/PopupMenu.java
Normal file
137
src/main/java/cn/octopusyan/dmt/view/PopupMenu.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package cn.octopusyan.dmt.view;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.CaptionMenuItem;
|
||||||
|
import cn.octopusyan.dmt.common.config.Constants;
|
||||||
|
import cn.octopusyan.dmt.common.util.ViewUtil;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Menu;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.control.SeparatorMenuItem;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.StageStyle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 托盘图标 菜单
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class PopupMenu {
|
||||||
|
// 用来隐藏弹出窗口的任务栏图标
|
||||||
|
private static final Stage utilityStage = new Stage();
|
||||||
|
// 菜单栏
|
||||||
|
private final ContextMenu root = new ContextMenu();
|
||||||
|
|
||||||
|
static {
|
||||||
|
utilityStage.initStyle(StageStyle.UTILITY);
|
||||||
|
utilityStage.setScene(new Scene(new Region()));
|
||||||
|
utilityStage.setOpacity(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu() {
|
||||||
|
|
||||||
|
root.focusedProperty().addListener((_, _, focused) -> {
|
||||||
|
if (!focused)
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
root.hide();
|
||||||
|
utilityStage.hide();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addItem(String label, EventHandler<ActionEvent> handler) {
|
||||||
|
return addItem(new MenuItem(label), handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addItem(StringBinding bind, EventHandler<ActionEvent> handler) {
|
||||||
|
MenuItem menuItem = new MenuItem();
|
||||||
|
menuItem.textProperty().bind(bind);
|
||||||
|
return addItem(menuItem, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addItem(MenuItem node, EventHandler<ActionEvent> handler) {
|
||||||
|
node.setOnAction(handler);
|
||||||
|
return addItem(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addSeparator() {
|
||||||
|
return addItem(new SeparatorMenuItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addCaptionItem() {
|
||||||
|
return addCaptionItem(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addCaptionItem(String title) {
|
||||||
|
return addItem(new CaptionMenuItem(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addMenu(String label, MenuItem... items) {
|
||||||
|
return addMenu(new Menu(label), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addMenu(StringBinding label, MenuItem... items) {
|
||||||
|
Menu menu = new Menu();
|
||||||
|
menu.textProperty().bind(label);
|
||||||
|
return addMenu(menu, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addMenu(Menu menu, MenuItem... items) {
|
||||||
|
menu.getItems().addAll(items);
|
||||||
|
return addItem(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addTitleItem() {
|
||||||
|
return addTitleItem(Constants.APP_TITLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addTitleItem(String label) {
|
||||||
|
return addExitItem(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addExitItem() {
|
||||||
|
return addExitItem("Exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopupMenu addExitItem(String label) {
|
||||||
|
return addItem(label, _ -> Platform.exit());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PopupMenu addItem(MenuItem node) {
|
||||||
|
root.getItems().add(node);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(java.awt.event.MouseEvent event) {
|
||||||
|
// 必须调用show才会隐藏任务栏图标
|
||||||
|
utilityStage.show();
|
||||||
|
|
||||||
|
if (root.isShowing())
|
||||||
|
root.hide();
|
||||||
|
|
||||||
|
root.show(utilityStage,
|
||||||
|
event.getX() / ViewUtil.scaleX,
|
||||||
|
event.getY() / ViewUtil.scaleY
|
||||||
|
);
|
||||||
|
// 获取焦点 (失去焦点隐藏自身)
|
||||||
|
root.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MenuItem menuItem(String label, EventHandler<ActionEvent> handler) {
|
||||||
|
MenuItem menuItem = new MenuItem(label);
|
||||||
|
menuItem.setOnAction(handler);
|
||||||
|
return menuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MenuItem menuItem(StringBinding stringBinding, EventHandler<ActionEvent> handler) {
|
||||||
|
MenuItem menuItem = new MenuItem();
|
||||||
|
menuItem.textProperty().bind(stringBinding);
|
||||||
|
menuItem.setOnAction(handler);
|
||||||
|
return menuItem;
|
||||||
|
}
|
||||||
|
}
|
113
src/main/java/cn/octopusyan/dmt/view/alert/AlertUtil.java
Normal file
113
src/main/java/cn/octopusyan/dmt/view/alert/AlertUtil.java
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package cn.octopusyan.dmt.view.alert;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.Application;
|
||||||
|
import cn.octopusyan.dmt.view.alert.builder.*;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹窗工具
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public class AlertUtil {
|
||||||
|
private final Window mOwner;
|
||||||
|
private static volatile AlertUtil alertUtil;
|
||||||
|
|
||||||
|
private AlertUtil(Window mOwner) {
|
||||||
|
this.mOwner = mOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized AlertUtil getInstance() {
|
||||||
|
if (alertUtil == null) {
|
||||||
|
alertUtil = new AlertUtil(Application.getPrimaryStage());
|
||||||
|
}
|
||||||
|
return alertUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertUtil getInstance(Stage stage) {
|
||||||
|
return new AlertUtil(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultBuilder builder() {
|
||||||
|
return new DefaultBuilder(mOwner, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultBuilder builder(boolean transparent) {
|
||||||
|
return new DefaultBuilder(mOwner, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder info(String content) {
|
||||||
|
return info().content(content).header(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder info() {
|
||||||
|
return alert(Alert.AlertType.INFORMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder error(String message) {
|
||||||
|
return alert(Alert.AlertType.ERROR).header(null).content(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder warning() {
|
||||||
|
return alert(Alert.AlertType.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder exception(Exception ex) {
|
||||||
|
return alert(Alert.AlertType.ERROR).exception(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认对话框
|
||||||
|
*/
|
||||||
|
public AlertBuilder confirm() {
|
||||||
|
return alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义确认对话框 <p>
|
||||||
|
*
|
||||||
|
* @param buttons <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
|
||||||
|
*/
|
||||||
|
public AlertBuilder confirm(String... buttons) {
|
||||||
|
return confirm().buttons(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder confirm(ButtonType... buttons) {
|
||||||
|
return confirm().buttons(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder alert(Alert.AlertType type) {
|
||||||
|
return new AlertBuilder(mOwner, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextInputBuilder input(String content) {
|
||||||
|
return new TextInputBuilder(mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextInputBuilder input(String content, String defaultResult) {
|
||||||
|
return new TextInputBuilder(mOwner, defaultResult).content(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final <T> ChoiceBuilder<T> choices(String hintText, T... choices) {
|
||||||
|
return new ChoiceBuilder<>(mOwner, choices).content(hintText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgressBuilder progress() {
|
||||||
|
return new ProgressBuilder(mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnChoseListener {
|
||||||
|
void confirm();
|
||||||
|
|
||||||
|
default void cancelOrClose(ButtonType buttonType) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnClickListener {
|
||||||
|
void onClicked(String result);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
package cn.octopusyan.dmt.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseBuilder;
|
||||||
|
import cn.octopusyan.dmt.common.config.LabelConstants;
|
||||||
|
import cn.octopusyan.dmt.view.alert.AlertUtil;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class AlertBuilder extends BaseBuilder<AlertBuilder, Alert> {
|
||||||
|
|
||||||
|
public AlertBuilder(Window owner, Alert.AlertType alertType) {
|
||||||
|
super(new Alert(alertType), owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder buttons(String... buttons) {
|
||||||
|
dialog.getButtonTypes().addAll(getButtonList(buttons));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder buttons(ButtonType... buttons) {
|
||||||
|
dialog.getButtonTypes().addAll(buttons);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlertBuilder exception(Exception ex) {
|
||||||
|
dialog.setTitle("Exception Dialog");
|
||||||
|
dialog.setHeaderText(ex.getClass().getSimpleName());
|
||||||
|
dialog.setContentText(ex.getMessage());
|
||||||
|
|
||||||
|
// 创建可扩展的异常。
|
||||||
|
var sw = new StringWriter();
|
||||||
|
var pw = new PrintWriter(sw);
|
||||||
|
ex.printStackTrace(pw);
|
||||||
|
var exceptionText = sw.toString();
|
||||||
|
|
||||||
|
var label = new Label("The exception stacktrace was :");
|
||||||
|
|
||||||
|
var textArea = new TextArea(exceptionText);
|
||||||
|
textArea.setEditable(false);
|
||||||
|
textArea.setWrapText(true);
|
||||||
|
|
||||||
|
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
textArea.setMaxHeight(Double.MAX_VALUE);
|
||||||
|
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||||
|
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||||
|
|
||||||
|
var expContent = new GridPane();
|
||||||
|
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
expContent.add(label, 0, 0);
|
||||||
|
expContent.add(textArea, 0, 1);
|
||||||
|
|
||||||
|
// 将可扩展异常设置到对话框窗格中。
|
||||||
|
dialog.getDialogPane().setExpandableContent(expContent);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取按钮列表
|
||||||
|
*
|
||||||
|
* @param buttons "Cancel" / "取消" 为取消按钮
|
||||||
|
*/
|
||||||
|
private List<ButtonType> getButtonList(String[] buttons) {
|
||||||
|
if (ArrayUtils.isEmpty(buttons)) return Collections.emptyList();
|
||||||
|
|
||||||
|
return Arrays.stream(buttons).map((type) -> {
|
||||||
|
ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER;
|
||||||
|
if ("cancel".equals(StringUtils.lowerCase(type)) || LabelConstants.CANCEL.equals(type)) {
|
||||||
|
return ButtonType.CANCEL;
|
||||||
|
}
|
||||||
|
return new ButtonType(type, buttonData);
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlertUtil.confirm
|
||||||
|
*/
|
||||||
|
public void show(AlertUtil.OnClickListener listener) {
|
||||||
|
Optional<ButtonType> result = dialog.showAndWait();
|
||||||
|
result.ifPresent(r -> listener.onClicked(r.getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlertUtil.confirm
|
||||||
|
*/
|
||||||
|
public void show(AlertUtil.OnChoseListener listener) {
|
||||||
|
Optional<ButtonType> result = dialog.showAndWait();
|
||||||
|
result.ifPresent(r -> {
|
||||||
|
if (r == ButtonType.OK) {
|
||||||
|
listener.confirm();
|
||||||
|
} else {
|
||||||
|
listener.cancelOrClose(r);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package cn.octopusyan.dmt.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseBuilder;
|
||||||
|
import javafx.scene.control.ChoiceDialog;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class ChoiceBuilder<R> extends BaseBuilder<ChoiceBuilder<R>, ChoiceDialog<R>> {
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public ChoiceBuilder(Window mOwner, R... choices) {
|
||||||
|
this(new ChoiceDialog<>(choices[0], choices), mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChoiceBuilder(ChoiceDialog<R> dialog, Window mOwner) {
|
||||||
|
super(dialog, mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlertUtil.choices
|
||||||
|
*/
|
||||||
|
public R showAndGetChoice() {
|
||||||
|
Optional<R> result = dialog.showAndWait();
|
||||||
|
return result.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package cn.octopusyan.dmt.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseBuilder;
|
||||||
|
import cn.octopusyan.dmt.common.util.ViewUtil;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.ButtonBar;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.Dialog;
|
||||||
|
import javafx.scene.control.DialogPane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.stage.StageStyle;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认弹窗
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class DefaultBuilder extends BaseBuilder<DefaultBuilder, Dialog<?>> {
|
||||||
|
|
||||||
|
public DefaultBuilder(Window mOwner) {
|
||||||
|
this(mOwner, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultBuilder(Window mOwner, boolean transparent) {
|
||||||
|
super(new Dialog<>(), mOwner);
|
||||||
|
|
||||||
|
header(null);
|
||||||
|
|
||||||
|
DialogPane dialogPane = dialog.getDialogPane();
|
||||||
|
if (transparent) {
|
||||||
|
dialogPane.getScene().setFill(Color.TRANSPARENT);
|
||||||
|
ViewUtil.bindDragged(dialogPane);
|
||||||
|
ViewUtil.bindShadow(dialogPane);
|
||||||
|
ViewUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogPane.getButtonTypes().add(new ButtonType("取消", ButtonType.CANCEL.getButtonData()));
|
||||||
|
|
||||||
|
for (Node child : dialogPane.getChildren()) {
|
||||||
|
if (child instanceof ButtonBar) {
|
||||||
|
dialogPane.getChildren().remove(child);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultBuilder content(Node content) {
|
||||||
|
dialog.getDialogPane().setContent(content);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package cn.octopusyan.dmt.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.config.LabelConstants;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ProgressBar;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载弹窗
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class ProgressBuilder extends DefaultBuilder {
|
||||||
|
|
||||||
|
private HBox hBox;
|
||||||
|
|
||||||
|
public ProgressBuilder(Window mOwner) {
|
||||||
|
super(mOwner);
|
||||||
|
content(getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWidth(double width) {
|
||||||
|
hBox.setPrefWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pane getContent() {
|
||||||
|
hBox = new HBox();
|
||||||
|
hBox.setPrefWidth(350);
|
||||||
|
hBox.setAlignment(Pos.CENTER);
|
||||||
|
hBox.setSpacing(10);
|
||||||
|
hBox.setPadding(new Insets(10, 0, 10, 0));
|
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
Button cancel = new Button(LabelConstants.CANCEL);
|
||||||
|
cancel.setCancelButton(true);
|
||||||
|
cancel.setOnAction(_ -> dialog.close());
|
||||||
|
|
||||||
|
// 进度条
|
||||||
|
ProgressBar progressBar = new ProgressBar(-1);
|
||||||
|
progressBar.prefWidthProperty().bind(Bindings.createDoubleBinding(
|
||||||
|
() -> hBox.widthProperty().get() - cancel.widthProperty().get() - 40,
|
||||||
|
hBox.widthProperty(), cancel.widthProperty()
|
||||||
|
));
|
||||||
|
|
||||||
|
hBox.getChildren().add(progressBar);
|
||||||
|
hBox.getChildren().add(cancel);
|
||||||
|
return hBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProgressBuilder onCancel(Runnable run) {
|
||||||
|
dialog.setOnCloseRequest(_ -> run.run());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package cn.octopusyan.dmt.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseBuilder;
|
||||||
|
import javafx.scene.control.TextInputDialog;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户输入弹窗
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class TextInputBuilder extends BaseBuilder<TextInputBuilder, TextInputDialog> {
|
||||||
|
|
||||||
|
public TextInputBuilder(Window mOwner) {
|
||||||
|
this(new TextInputDialog(), mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextInputBuilder(Window mOwner, String defaultResult) {
|
||||||
|
this(new TextInputDialog(defaultResult), mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextInputBuilder(TextInputDialog dialog, Window mOwner) {
|
||||||
|
super(dialog, mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AlertUtil.input
|
||||||
|
* 如果用户点击了取消按钮,将会返回null
|
||||||
|
*/
|
||||||
|
public String getInput() {
|
||||||
|
Optional<String> result = dialog.showAndWait();
|
||||||
|
return result.orElse(null);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
package cn.octopusyan.dmt.view.filemanager;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Tweaks;
|
||||||
|
import cn.octopusyan.dmt.utils.FileUtil;
|
||||||
|
import javafx.scene.control.TreeCell;
|
||||||
|
import javafx.scene.control.TreeItem;
|
||||||
|
import javafx.scene.control.TreeView;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public final class DirectoryTree extends TreeView<File> {
|
||||||
|
public static final FileIconRepository fileIcon = new FileIconRepository();
|
||||||
|
|
||||||
|
// 文件夹在前
|
||||||
|
static final Comparator<TreeItem<File>> FILE_TYPE_COMPARATOR = Comparator.comparing(
|
||||||
|
item -> !Files.isDirectory(item.getValue().toPath())
|
||||||
|
);
|
||||||
|
|
||||||
|
public DirectoryTree() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
getStyleClass().add(Tweaks.ALT_ICON);
|
||||||
|
|
||||||
|
setCellFactory(_ -> new TreeCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(File item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
if (empty || item == null) {
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
} else {
|
||||||
|
setText(item.getName());
|
||||||
|
|
||||||
|
var image = new ImageView(item.isDirectory() ?
|
||||||
|
FileIconRepository.FOLDER :
|
||||||
|
fileIcon.getByMimeType(FileUtil.getMimeType(item.toPath()))
|
||||||
|
);
|
||||||
|
image.setFitWidth(20);
|
||||||
|
image.setFitHeight(20);
|
||||||
|
setGraphic(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadRoot(File value) {
|
||||||
|
var root = new TreeItem<>(value);
|
||||||
|
root.setExpanded(true);
|
||||||
|
setRoot(root);
|
||||||
|
|
||||||
|
// scan file tree two levels deep for starters
|
||||||
|
scan(root, 5);
|
||||||
|
|
||||||
|
// scan deeper as the user navigates down the tree
|
||||||
|
root.addEventHandler(TreeItem.branchExpandedEvent(), event -> {
|
||||||
|
TreeItem parent = event.getTreeItem();
|
||||||
|
parent.getChildren().forEach(child -> {
|
||||||
|
var item = (TreeItem<File>) child;
|
||||||
|
if (item.getChildren().isEmpty()) {
|
||||||
|
scan(item, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void scan(TreeItem<File> parent, int depth) {
|
||||||
|
File[] files = parent.getValue().listFiles();
|
||||||
|
depth--;
|
||||||
|
|
||||||
|
if (files != null) {
|
||||||
|
for (File f : files) {
|
||||||
|
var item = new TreeItem<>(f);
|
||||||
|
parent.getChildren().add(item);
|
||||||
|
|
||||||
|
if (depth > 0) {
|
||||||
|
scan(item, depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 文件类型+名称排序
|
||||||
|
parent.getChildren().sort(FILE_TYPE_COMPARATOR.thenComparing(TreeItem::getValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
package cn.octopusyan.dmt.view.filemanager;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.utils.Resources;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import javax.swing.filechooser.FileSystemView;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class FileIconRepository {
|
||||||
|
|
||||||
|
public static final String IMAGE_DIRECTORY = "images/papirus/";
|
||||||
|
public static final Image UNKNOWN_FILE = new Image(
|
||||||
|
Resources.getResourceAsStream(IMAGE_DIRECTORY + "mimetypes/text-plain.png")
|
||||||
|
);
|
||||||
|
public static final Image FOLDER = new Image(
|
||||||
|
Resources.getResourceAsStream(IMAGE_DIRECTORY + "places/folder-paleorange.png")
|
||||||
|
);
|
||||||
|
|
||||||
|
private final Map<String, Image> cache = new HashMap<>();
|
||||||
|
private final Set<String> unknownMimeTypes = new HashSet<>();
|
||||||
|
|
||||||
|
public Image getByMimeType(String mimeType) {
|
||||||
|
if (mimeType == null || unknownMimeTypes.contains(mimeType)) {
|
||||||
|
return UNKNOWN_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cachedImage = cache.get(mimeType);
|
||||||
|
if (cachedImage != null) {
|
||||||
|
return cachedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = mimeType.replaceAll("/", "-") + ".png";
|
||||||
|
|
||||||
|
try {
|
||||||
|
var image = new Image(Resources.getResourceAsStream(IMAGE_DIRECTORY + "mimetypes/" + fileName));
|
||||||
|
cache.put(mimeType, image);
|
||||||
|
return image;
|
||||||
|
} catch (Exception e) {
|
||||||
|
unknownMimeTypes.add(mimeType);
|
||||||
|
return UNKNOWN_FILE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileName(File file) {
|
||||||
|
return FileSystemView.getFileSystemView().getSystemDisplayName(file);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package cn.octopusyan.dmt.viewModel;
|
||||||
|
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseViewModel;
|
||||||
|
import cn.octopusyan.dmt.controller.help.AboutController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关于
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class AboutViewModel extends BaseViewModel<AboutController> {
|
||||||
|
}
|
210
src/main/java/cn/octopusyan/dmt/viewModel/MainViewModel.java
Normal file
210
src/main/java/cn/octopusyan/dmt/viewModel/MainViewModel.java
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
package cn.octopusyan.dmt.viewModel;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Styles;
|
||||||
|
import cn.octopusyan.dmt.common.base.BaseViewModel;
|
||||||
|
import cn.octopusyan.dmt.controller.MainController;
|
||||||
|
import cn.octopusyan.dmt.model.WordItem;
|
||||||
|
import cn.octopusyan.dmt.task.PackTask;
|
||||||
|
import cn.octopusyan.dmt.task.TranslateTask;
|
||||||
|
import cn.octopusyan.dmt.task.UnpackTask;
|
||||||
|
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
|
||||||
|
import cn.octopusyan.dmt.translate.DelayWord;
|
||||||
|
import cn.octopusyan.dmt.translate.TranslateUtil;
|
||||||
|
import cn.octopusyan.dmt.view.ConsoleLog;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.concurrent.Worker;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.kordamp.ikonli.feather.Feather;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.DelayQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主界面
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class MainViewModel extends BaseViewModel<MainController> {
|
||||||
|
private static final ConsoleLog consoleLog = ConsoleLog.getInstance(MainViewModel.class);
|
||||||
|
/**
|
||||||
|
* 解包任务
|
||||||
|
*/
|
||||||
|
private UnpackTask unpackTask;
|
||||||
|
/**
|
||||||
|
* 翻译任务
|
||||||
|
*/
|
||||||
|
private TranslateTask translateTask;
|
||||||
|
private DelayQueue<DelayWord> delayQueue;
|
||||||
|
private String unpackPath;
|
||||||
|
private int total;
|
||||||
|
|
||||||
|
FontIcon startIcon = new FontIcon(Feather.PLAY);
|
||||||
|
FontIcon pauseIcon = new FontIcon(Feather.PAUSE);
|
||||||
|
private List<WordItem> wordItems;
|
||||||
|
|
||||||
|
private final StringProperty fileName = new SimpleStringProperty();
|
||||||
|
|
||||||
|
public StringProperty fileNameProperty() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载PBO文件
|
||||||
|
*/
|
||||||
|
public void selectFile(File pboFile) {
|
||||||
|
if (pboFile == null) return;
|
||||||
|
|
||||||
|
fileName.setValue(pboFile.getAbsolutePath());
|
||||||
|
|
||||||
|
unpackTask = new UnpackTask(pboFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包
|
||||||
|
*/
|
||||||
|
public void unpack() {
|
||||||
|
if (unpackTask == null) return;
|
||||||
|
|
||||||
|
unpackTask.onListen(new UnpackTask.UnpackListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRunning() {
|
||||||
|
// 展示加载
|
||||||
|
controller.onLoad();
|
||||||
|
// 重置进度
|
||||||
|
resetProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnpackOver(String path) {
|
||||||
|
MainViewModel.this.unpackPath = path;
|
||||||
|
Platform.runLater(() -> controller.onUnpack(new File(path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindWordOver(List<WordItem> wordItems) {
|
||||||
|
total = wordItems.size();
|
||||||
|
MainViewModel.this.wordItems = wordItems;
|
||||||
|
Platform.runLater(() -> controller.onLoadWord(wordItems));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
unpackTask.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始翻译
|
||||||
|
*/
|
||||||
|
public void startTranslate() {
|
||||||
|
if(wordItems.isEmpty()) return;
|
||||||
|
|
||||||
|
if (translateTask == null) {
|
||||||
|
List<WordItem> words = wordItems.stream().filter(item -> StringUtils.isEmpty(item.getChinese())).toList();
|
||||||
|
delayQueue = TranslateUtil.getDelayQueue(words);
|
||||||
|
translateTask = createTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!translateTask.isRunning()) {
|
||||||
|
// 检查进度
|
||||||
|
if (!delayQueue.isEmpty()) {
|
||||||
|
AtomicInteger index = new AtomicInteger(0);
|
||||||
|
delayQueue.forEach(item -> TranslateUtil.resetDelayTime(index.getAndIncrement(), item));
|
||||||
|
translateTask = createTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translateTask.getState() != Worker.State.SUCCEEDED) {
|
||||||
|
translateTask.execute();
|
||||||
|
// 展示进度
|
||||||
|
controller.translateProgress.setVisible(true);
|
||||||
|
controller.translateProgress.progressProperty().bind(translateTask.progressProperty());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
translateTask.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包
|
||||||
|
*/
|
||||||
|
public void pack() {
|
||||||
|
if(wordItems.isEmpty()) return;
|
||||||
|
|
||||||
|
PackTask packTask = new PackTask(wordItems, unpackPath);
|
||||||
|
packTask.onListen(new PackTask.PackListener() {
|
||||||
|
@Override
|
||||||
|
public void onWriteOver() {
|
||||||
|
consoleLog.info("写入完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPackOver(File file) {
|
||||||
|
Platform.runLater(() -> controller.onPackOver(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
packTask.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TranslateTask createTask() {
|
||||||
|
TranslateTask task = new TranslateTask(delayQueue, total);
|
||||||
|
task.onListen(new DefaultTaskListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRunning() {
|
||||||
|
ProgressIndicator graphic = new ProgressIndicator();
|
||||||
|
graphic.setPrefWidth(15);
|
||||||
|
graphic.setPrefHeight(15);
|
||||||
|
graphic.setOnMouseClicked(_ -> controller.startTranslate());
|
||||||
|
controller.translate.setGraphic(graphic);
|
||||||
|
controller.translateProgress.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled() {
|
||||||
|
task.getThreadPoolManager().shutdownNow();
|
||||||
|
TranslateTask.consoleLog.info("翻译暂停");
|
||||||
|
Platform.runLater(() -> controller.translate.setGraphic(pauseIcon));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSucceed() {
|
||||||
|
if (delayQueue.isEmpty()) {
|
||||||
|
Platform.runLater(() -> controller.translate.setGraphic(startIcon));
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> controller.translate.setGraphic(pauseIcon));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载PBO文件后,重置进度
|
||||||
|
*/
|
||||||
|
private void resetProgress() {
|
||||||
|
translateTask = null;
|
||||||
|
controller.translate.setGraphic(startIcon);
|
||||||
|
Styles.toggleStyleClass(controller.translateProgress, Styles.SMALL);
|
||||||
|
controller.translateProgress.progressProperty().unbind();
|
||||||
|
controller.translateProgress.setProgress(0);
|
||||||
|
controller.translateProgress.setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 给定字符串是否含有中文
|
||||||
|
*
|
||||||
|
* @param str 需要判断的字符串
|
||||||
|
* @return 是否含有中文
|
||||||
|
*/
|
||||||
|
private boolean containsChinese(String str) {
|
||||||
|
return Pattern.compile("[\u4e00-\u9fa5]").matcher(str).find();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user