mirror of
https://github.com/octopusYan/alist-gui.git
synced 2025-12-08 17:21:56 +08:00
Compare commits
45 Commits
v0.0.1
...
alpha/v1.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0660674f7f | |||
| 1b3a4f5569 | |||
| be7e17665f | |||
| 654b13e150 | |||
| 329c484f4c | |||
| ec585ea29d | |||
| 5a8e121fdd | |||
| d4dcbf6e64 | |||
| fb6c854dc0 | |||
| 9c75f9bf3a | |||
| 08f0473814 | |||
| 442940cf05 | |||
| 12d9a07320 | |||
| 9f5eaba2c8 | |||
| 7c051bbf44 | |||
| d65990791a | |||
| c93837a7a9 | |||
| cfd7075c8f | |||
| 80bda287cc | |||
| ef98a76cd3 | |||
| 1f6ba2d8cc | |||
| a0e5e16afc | |||
| a9dd63b251 | |||
| 88a2f705ba | |||
| c3cbbd497f | |||
| 09ca87f4f4 | |||
| 95404edc92 | |||
| d6609a5d75 | |||
| 7aaf2db034 | |||
| 8588ad8c47 | |||
| f18ff10c92 | |||
| 8d0a3d616c | |||
| 956f7fd7ba | |||
| b1218e9122 | |||
| ef8ae3e53d | |||
| e00a742fff | |||
| 4692e4b436 | |||
| fdbafdbb3f | |||
| d1368daa61 | |||
| edf574c0af | |||
| 6858dfcca8 | |||
| 55ea1d8a80 | |||
| 7b10186dab | |||
| fecdee3664 | |||
| 7599524df1 |
79
.github/workflows/auto-alpha-tag.yml
vendored
Normal file
79
.github/workflows/auto-alpha-tag.yml
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
name: Auto Alpha Tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "dev"
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/*.yml"
|
||||||
|
- "src/**"
|
||||||
|
- "pom.xml"
|
||||||
|
- "!**/*.md"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "dev"
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-tag:
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
actions: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- msbuild_target: x64
|
||||||
|
lowercase_target: x64
|
||||||
|
fail-fast: false
|
||||||
|
outputs:
|
||||||
|
tag: ${{ steps.set_tag.outputs.tag }}
|
||||||
|
pre_version: ${{ steps.set_tag.outputs.pre_version }}
|
||||||
|
main_tag_name: ${{ steps.push_main_tag.outputs.main_tag_name }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install semver
|
||||||
|
run: |
|
||||||
|
npm install --global --progress semver
|
||||||
|
|
||||||
|
- name: Set tag
|
||||||
|
id: set_tag
|
||||||
|
run: |
|
||||||
|
# pre_version是上一个公版,这里需要拉上一个tag,避免堆积过多commit
|
||||||
|
$latest_tag=$(git describe --tags --abbrev=0)
|
||||||
|
echo "latest_tag=$latest_tag" >> $env:GITHUB_OUTPUT
|
||||||
|
$described = $(git describe --tags --long --match 'v*')
|
||||||
|
$ids = $($described -split "-")
|
||||||
|
if ($ids.length -eq 3) {
|
||||||
|
$ver = "v$(semver --increment $ids[0].Substring(1))"
|
||||||
|
$pre_version = "$($ids[0])"
|
||||||
|
$dist = `printf "%03d"` $ids[1]
|
||||||
|
echo "tag=$ver-alpha.1.d$($dist).$($ids[2])" >> $env:GITHUB_OUTPUT
|
||||||
|
echo "pre_version=$pre_version" >> $env:GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
if ($ids.length -eq 4) {
|
||||||
|
$dist = `printf "%03d"` $ids[2]
|
||||||
|
$pre_version = "$($ids[0])-$($ids[1])"
|
||||||
|
echo "pre_version=$pre_version" >> $env:GITHUB_OUTPUT
|
||||||
|
echo "tag=$pre_version.d$($dist).$($ids[3])" >> $env:GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Push tag to main repo
|
||||||
|
id: push_main_tag
|
||||||
|
run: |
|
||||||
|
git config user.name 'github-actions[bot]'
|
||||||
|
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
|
|
||||||
|
$main_tag_name=$(echo "alpha/${{ steps.set_tag.outputs.tag }}")
|
||||||
|
git tag $main_tag_name -f
|
||||||
|
git push --tags origin HEAD:refs/tags/$main_tag_name -f
|
||||||
|
echo "main_tag_name=$main_tag_name" >> $env:GITHUB_OUTPUT
|
||||||
52
.github/workflows/auto-release-tag.yml
vendored
Normal file
52
.github/workflows/auto-release-tag.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
name: Auto Tag Release PR
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
type: string
|
||||||
|
description: Release
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto_tag_release:
|
||||||
|
if: github.event.pull_request.merged == true && (startsWith(github.event.pull_request.title, 'Release v') || startsWith(github.event.pull_request.title, 'release v')) || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ secrets.ALISTGUI_RELEASE }}
|
||||||
|
|
||||||
|
- name: Git config
|
||||||
|
run: |
|
||||||
|
git config user.name "$GITHUB_ACTOR"
|
||||||
|
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Extract tag name
|
||||||
|
id: extract_tag
|
||||||
|
run: |
|
||||||
|
if ${{ github.event_name != 'workflow_dispatch' }}; then
|
||||||
|
tag_name=$(echo "${{ github.event.pull_request.title }}" | sed -E 's/(Release|release)//' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||||
|
echo "tag_name=$tag_name" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "tag_name=${{ inputs.tag }}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create release tag and push
|
||||||
|
run: |
|
||||||
|
git tag -a "${{ steps.extract_tag.outputs.tag_name }}" -m "${{ steps.extract_tag.outputs.tag_name }}" -f
|
||||||
|
git push origin "${{ steps.extract_tag.outputs.tag_name }}"
|
||||||
|
|
||||||
|
- name: Merge into dev and push
|
||||||
|
run: |
|
||||||
|
git switch dev
|
||||||
|
git merge "${{ steps.extract_tag.outputs.tag_name }}"
|
||||||
|
git push origin dev
|
||||||
154
.github/workflows/release.yml
vendored
Normal file
154
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
branches-ignore:
|
||||||
|
- "master"
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/*.yml"
|
||||||
|
- "src/**"
|
||||||
|
- "pom.xml"
|
||||||
|
- "!**/*.md"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "dev"
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/*.yml"
|
||||||
|
- "src/**"
|
||||||
|
- "pom.xml"
|
||||||
|
- "!**/*.md"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
meta:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
tag: ${{ steps.set_tag.outputs.tag }}
|
||||||
|
prerelease: ${{ steps.set_pre.outputs.prerelease }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: temp
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
|
- name: Fetch history
|
||||||
|
if: ${{ !startsWith(github.ref, 'refs/pull/') }}
|
||||||
|
run: |
|
||||||
|
git init
|
||||||
|
cp $GITHUB_WORKSPACE/temp/.git/config ./.git
|
||||||
|
rm -rf $GITHUB_WORKSPACE/temp
|
||||||
|
# git config remote.origin.fetch '+refs/*:refs/*'
|
||||||
|
git fetch --filter=tree:0 # --update-head-ok
|
||||||
|
git reset --hard origin/$(git branch --show-current) || true
|
||||||
|
git checkout ${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Set tag
|
||||||
|
id: set_tag
|
||||||
|
run: |
|
||||||
|
${{ startsWith(github.ref, 'refs/pull/') && 'cd temp' || '' }}
|
||||||
|
echo tag=$(git describe --tags --match "v*" ${{ github.ref }} || git rev-parse --short HEAD) | tee -a $GITHUB_OUTPUT
|
||||||
|
exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
- name: Judge pre-release
|
||||||
|
id: set_pre
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
run: |
|
||||||
|
if [[ '${{ steps.set_tag.outputs.tag }}' =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo prerelease=false | tee -a $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo prerelease=true | tee -a $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# - name: Set up Node.js
|
||||||
|
# uses: actions/setup-node@v4
|
||||||
|
# with:
|
||||||
|
# node-version: 14
|
||||||
|
#
|
||||||
|
# - name: Install dependencies
|
||||||
|
# run: npm install conventional-changelog-cli
|
||||||
|
|
||||||
|
- name: Generate Changelog
|
||||||
|
run: |
|
||||||
|
this_tag=${{ steps.set_tag.outputs.tag }}
|
||||||
|
if [[ '${{ steps.set_pre.outputs.prerelease }}' != 'false' ]]; then
|
||||||
|
last_tag=$(git describe --tags --match "v*" --abbrev=0 --exclude='${{ steps.set_tag.outputs.tag }}')
|
||||||
|
else
|
||||||
|
last_tag=$(git describe --tags --match "v*" --abbrev=0 --exclude='${{ steps.set_tag.outputs.tag }}' --exclude='*-*')
|
||||||
|
fi
|
||||||
|
echo >> CHANGELOG.md
|
||||||
|
echo "**Full Changelog**: [$last_tag -> $this_tag](https://github.com/octopusYan/alist-gui/compare/${last_tag}...${this_tag})" >> CHANGELOG.md
|
||||||
|
|
||||||
|
- name: Upload changelog to Github
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
with:
|
||||||
|
name: changelog
|
||||||
|
path: CHANGELOG.md
|
||||||
|
|
||||||
|
windows:
|
||||||
|
needs: meta
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- msbuild_target: x64
|
||||||
|
lowercase_target: x64
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: 21
|
||||||
|
distribution: 'dragonwell'
|
||||||
|
architecture: x64
|
||||||
|
cache: maven
|
||||||
|
|
||||||
|
- name: Build with Maven
|
||||||
|
run: |
|
||||||
|
mvn clean package -f pom.xml
|
||||||
|
mkdir zipball && cp target/*-windows.zip zipball
|
||||||
|
|
||||||
|
- name: Upload AListGUI to Github
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AListGUI-windows
|
||||||
|
path: zipball
|
||||||
|
|
||||||
|
release:
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
needs: [ meta, windows ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
actions: write
|
||||||
|
steps:
|
||||||
|
- name: Download AListGUI from Github
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: assets
|
||||||
|
|
||||||
|
- name: Cleanup files
|
||||||
|
run: |
|
||||||
|
mv -vf assets/changelog/* .
|
||||||
|
cd assets
|
||||||
|
find . -type f | while read f; do mv -fvt . $f; done
|
||||||
|
|
||||||
|
- name: Release to Github
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
body_path: CHANGELOG.md
|
||||||
|
files: |
|
||||||
|
assets/*
|
||||||
|
prerelease: ${{ needs.meta.outputs.prerelease != 'false' }}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,6 +6,8 @@ target/
|
|||||||
!.mvn/wrapper/maven-wrapper.jar
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
!**/src/main/**/target/
|
!**/src/main/**/target/
|
||||||
!**/src/test/**/target/
|
!**/src/test/**/target/
|
||||||
|
/config/
|
||||||
|
/bin/
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
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/>.
|
||||||
58
README.md
Normal file
58
README.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<div>
|
||||||
|
|
||||||
|
# AList GUI
|
||||||
|
|
||||||
|

|
||||||
|
[](https://openjfx.io/)
|
||||||
|

|
||||||
|
<br>
|
||||||
|
[](https://github.com/octopusYan/alist-gui)
|
||||||
|

|
||||||
|
<br>
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
使用 JavaFx 编写的 (仿[AList Desktop](https://ad.nn.ci/zh)) Windows GUI
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
#### 本地运行
|
||||||
|
|
||||||
|
1. 克隆代码
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/octopusYan/alist-gui
|
||||||
|
```
|
||||||
|
2. 运行
|
||||||
|
```bash
|
||||||
|
mvn clean javafx:run
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 构建
|
||||||
|
|
||||||
|
1. 克隆代码
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/octopusYan/alist-gui
|
||||||
|
```
|
||||||
|
2. 运行
|
||||||
|
```bash
|
||||||
|
mvn package
|
||||||
|
```
|
||||||
|
|
||||||
|
### 依赖/引用的项目
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|-----------------------------------------------------------------------------|-----------------|
|
||||||
|
| [JavaFX](https://openjfx.io/) | Java 桌面开发 |
|
||||||
|
| [AtlantaFX](https://mkpaz.github.io/atlantafx/) | JavaFX CSS 主题集合 |
|
||||||
|
| [JavaPackager](https://github.com/fvarrui/JavaPackager) | 打包插件 |
|
||||||
|
| [Ikonli](https://kordamp.org/ikonli/) | 图标库 |
|
||||||
|
| [Gluon-Emoji](https://github.com/gluonhq/emoji) | emoji |
|
||||||
|
| [Apache Commons](https://commons.apache.org/proper/commons-exec/index.html) | 工具包 |
|
||||||
|
| [Hutool](https://doc.hutool.cn/pages/index/) | 工具类库 |
|
||||||
|
| [SLF4J](https://slf4j.org/) | 日志工具 |
|
||||||
|
|
||||||
|
</figure>
|
||||||
203
pom.xml
203
pom.xml
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>cn.octopusyan</groupId>
|
<groupId>cn.octopusyan</groupId>
|
||||||
<artifactId>alist-gui</artifactId>
|
<artifactId>alist-gui</artifactId>
|
||||||
<version>0.1.0</version>
|
<version>1.0.0</version>
|
||||||
<name>alist-gui</name>
|
<name>alist-gui</name>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
@ -15,24 +15,31 @@
|
|||||||
</organization>
|
</organization>
|
||||||
|
|
||||||
<inceptionYear>2024</inceptionYear>
|
<inceptionYear>2024</inceptionYear>
|
||||||
<description>alist windows gui</description>
|
<description>AList GUI</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>17</maven.compiler.source>
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
<maven.compiler.target>17</maven.compiler.target>
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
<java.version>17</java.version>
|
<java.version>21</java.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
|
||||||
|
<exec.mainClass>cn.octopusyan.alistgui.AppLauncher</exec.mainClass>
|
||||||
|
<cssSrcPath>${project.basedir}/src/main/resources/css</cssSrcPath>
|
||||||
|
<cssTargetPath>${project.basedir}/target/classes/css</cssTargetPath>
|
||||||
|
|
||||||
<junit.version>5.10.0</junit.version>
|
<junit.version>5.10.0</junit.version>
|
||||||
<javafx.version>17.0.6</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.4.14</logback.version>
|
<logback.version>1.4.14</logback.version>
|
||||||
<fastjson.version>2.0.52</fastjson.version>
|
<hutool.version>5.8.32</hutool.version>
|
||||||
<hutool.version>5.8.25</hutool.version>
|
|
||||||
<common-lang3.version>3.16.0</common-lang3.version>
|
<common-lang3.version>3.16.0</common-lang3.version>
|
||||||
<common-io.version>2.16.1</common-io.version>
|
|
||||||
<common-exec.version>1.4.0</common-exec.version>
|
<common-exec.version>1.4.0</common-exec.version>
|
||||||
|
<jna.version>5.14.0</jna.version>
|
||||||
|
<lombok.version>1.18.32</lombok.version>
|
||||||
|
<jackson.version>2.15.4</jackson.version>
|
||||||
|
<ikonli.version>12.3.1</ikonli.version>
|
||||||
|
<gluonhq-emoji.version>1.0.1</gluonhq-emoji.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -48,6 +55,13 @@
|
|||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mkpaz.github.io/atlantafx/ -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.mkpaz</groupId>
|
||||||
|
<artifactId>atlantafx-base</artifactId>
|
||||||
|
<version>2.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- slf4j -->
|
<!-- slf4j -->
|
||||||
<!-- https://slf4j.org/manual.html -->
|
<!-- https://slf4j.org/manual.html -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -66,20 +80,6 @@
|
|||||||
<version>${logback.version}</version>
|
<version>${logback.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- junit -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.jupiter</groupId>
|
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
|
||||||
<version>${junit.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.jupiter</groupId>
|
|
||||||
<artifactId>junit-jupiter-engine</artifactId>
|
|
||||||
<version>${junit.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- common -->
|
<!-- common -->
|
||||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -87,12 +87,6 @@
|
|||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>${common-lang3.version}</version>
|
<version>${common-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-io</groupId>
|
|
||||||
<artifactId>commons-io</artifactId>
|
|
||||||
<version>${common-io.version}</version>
|
|
||||||
</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>
|
||||||
@ -100,89 +94,172 @@
|
|||||||
<version>${common-exec.version}</version>
|
<version>${common-exec.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JSON -->
|
<!-- hutool -->
|
||||||
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.fastjson2</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>fastjson2</artifactId>
|
<artifactId>hutool-core</artifactId>
|
||||||
<version>${fastjson.version}</version>
|
<version>${hutool.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.kordamp.ikonli/ikonli-javafx -->
|
<!-- lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<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>
|
<dependency>
|
||||||
<groupId>org.kordamp.ikonli</groupId>
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
<artifactId>ikonli-javafx</artifactId>
|
<artifactId>ikonli-javafx</artifactId>
|
||||||
<version>12.3.1</version>
|
<version>${ikonli.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.kordamp.ikonli</groupId>
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
<artifactId>ikonli-coreui-pack</artifactId>
|
<artifactId>ikonli-fontawesome-pack</artifactId>
|
||||||
<version>12.3.1</version>
|
<version>${ikonli.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.gluonhq</groupId>
|
||||||
|
<artifactId>emoji</artifactId>
|
||||||
|
<version>${gluonhq-emoji.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>nexus</id>
|
||||||
|
<name>nexus-snapshot-repository</name>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
<updatePolicy>always</updatePolicy>
|
||||||
|
</snapshots>
|
||||||
|
<releases>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</releases>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<resources>
|
<resources>
|
||||||
<resource>
|
<resource>
|
||||||
<directory>src/main/resources</directory>
|
<directory>src/main/resources</directory>
|
||||||
<filtering>true</filtering>
|
<filtering>true</filtering>
|
||||||
|
<excludes>
|
||||||
|
<exclude>css/*.scss</exclude>
|
||||||
|
</excludes>
|
||||||
</resource>
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<version>3.11.0</version>
|
|
||||||
<configuration>
|
|
||||||
<source>17</source>
|
|
||||||
<target>17</target>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.2.0</version>
|
<version>3.13.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<nonFilteredFileExtensions>
|
<source>21</source>
|
||||||
<nonFilteredFileExtension>exe</nonFilteredFileExtension>
|
<target>21</target>
|
||||||
<nonFilteredFileExtension>dll</nonFilteredFileExtension>
|
<compilerArgs>--enable-preview</compilerArgs>
|
||||||
</nonFilteredFileExtensions>
|
<encoding>UTF-8</encoding>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<!-- https://github.com/HebiRobotics/sass-cli-maven-plugin -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>us.hebi.sass</groupId>
|
||||||
|
<artifactId>sass-cli-maven-plugin</artifactId>
|
||||||
|
<version>1.0.3</version>
|
||||||
|
<configuration>
|
||||||
|
<sassVersion>1.78.0</sassVersion>
|
||||||
|
<args> <!-- Any argument that should be forwarded to the sass cli -->
|
||||||
|
<arg>${cssSrcPath}/root.scss:${cssTargetPath}/root.css</arg>
|
||||||
|
<arg>${cssSrcPath}/root-view.scss:${cssTargetPath}/root-view.css</arg>
|
||||||
|
<arg>${cssSrcPath}/main-view.scss:${cssTargetPath}/main-view.css</arg>
|
||||||
|
<arg>${cssSrcPath}/setup-view.scss:${cssTargetPath}/setup-view.css</arg>
|
||||||
|
<arg>${cssSrcPath}/about-view.scss:${cssTargetPath}/about-view.css</arg>
|
||||||
|
<arg>${cssSrcPath}/admin-panel.scss:${cssTargetPath}/admin-panel.css</arg>
|
||||||
|
<arg>--no-source-map</arg>
|
||||||
|
</args>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>sass-exec</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<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>alistgui</launcher>
|
||||||
|
<jlinkImageName>app</jlinkImageName>
|
||||||
|
<jlinkZipName>app</jlinkZipName>
|
||||||
|
<mainClass>cn.octopusyan.alistgui/${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>cn.octopusyan.alistgui/cn.octopusyan.alistgui.AppLuncher
|
|
||||||
</mainClass>
|
|
||||||
<launcher>launcher</launcher>
|
|
||||||
<jlinkZipName>app</jlinkZipName>
|
|
||||||
<jlinkImageName>app</jlinkImageName>
|
|
||||||
<noManPages>true</noManPages>
|
|
||||||
<stripDebug>true</stripDebug>
|
<stripDebug>true</stripDebug>
|
||||||
|
<compress>2</compress>
|
||||||
<noHeaderFiles>true</noHeaderFiles>
|
<noHeaderFiles>true</noHeaderFiles>
|
||||||
|
<noManPages>true</noManPages>
|
||||||
|
<launcher>alist-gui</launcher>
|
||||||
|
<jlinkImageName>app</jlinkImageName>
|
||||||
|
<jlinkZipName>app</jlinkZipName>
|
||||||
|
<mainClass>cn.octopusyan.alistgui/${exec.mainClass}</mainClass>
|
||||||
|
<options>
|
||||||
|
<option>--enable-preview</option>
|
||||||
|
</options>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<!-- https://github.com/fvarrui/JavaPackager -->
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>io.github.fvarrui</groupId>
|
<groupId>io.github.fvarrui</groupId>
|
||||||
<artifactId>javapackager</artifactId>
|
<artifactId>javapackager</artifactId>
|
||||||
<version>1.7.5</version>
|
<version>1.7.7-SNAPSHOT</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
|
<mainClass>${exec.mainClass}</mainClass>
|
||||||
<bundleJre>true</bundleJre>
|
<bundleJre>true</bundleJre>
|
||||||
<mainClass>cn.octopusyan.alistgui.AppLuncher</mainClass>
|
|
||||||
<generateInstaller>false</generateInstaller>
|
<generateInstaller>false</generateInstaller>
|
||||||
|
<copyDependencies>true</copyDependencies>
|
||||||
|
<assetsDir>${project.basedir}/src/main/resources/assets</assetsDir>
|
||||||
|
<vmArgs>
|
||||||
|
<arg>--enable-preview</arg>
|
||||||
|
<arg>-Xmx100m</arg>
|
||||||
|
<!-- <arg>-Djava.awt.headless=false</arg>-->
|
||||||
|
</vmArgs>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
@ -194,6 +271,10 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<platform>windows</platform>
|
<platform>windows</platform>
|
||||||
<createZipball>true</createZipball>
|
<createZipball>true</createZipball>
|
||||||
|
<winConfig>
|
||||||
|
<headerType>gui</headerType>
|
||||||
|
<generateMsi>false</generateMsi>
|
||||||
|
</winConfig>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ package cn.octopusyan.alistgui;
|
|||||||
*
|
*
|
||||||
* @author octopus_yan@foxmail.com
|
* @author octopus_yan@foxmail.com
|
||||||
*/
|
*/
|
||||||
public class AppLuncher {
|
public class AppLauncher {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Application.launch(Application.class, args);
|
Application.launch(Application.class, args);
|
||||||
@ -1,35 +1,58 @@
|
|||||||
package cn.octopusyan.alistgui;
|
package cn.octopusyan.alistgui;
|
||||||
|
|
||||||
import cn.octopusyan.alistgui.config.AppConstant;
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
import cn.octopusyan.alistgui.config.CustomConfig;
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
import cn.octopusyan.alistgui.controller.MainController;
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.alistgui.manager.SystemTrayManager;
|
||||||
import cn.octopusyan.alistgui.manager.http.HttpConfig;
|
import cn.octopusyan.alistgui.manager.http.HttpConfig;
|
||||||
import cn.octopusyan.alistgui.manager.http.HttpUtil;
|
import cn.octopusyan.alistgui.manager.http.HttpUtil;
|
||||||
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
|
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
|
||||||
import cn.octopusyan.alistgui.util.AlertUtil;
|
import cn.octopusyan.alistgui.util.ProcessesUtil;
|
||||||
import cn.octopusyan.alistgui.util.FxmlUtil;
|
import cn.octopusyan.alistgui.view.alert.AlertUtil;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.StageStyle;
|
import javafx.stage.StageStyle;
|
||||||
|
import lombok.Getter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ProxySelector;
|
import java.net.ProxySelector;
|
||||||
|
import java.net.http.HttpClient;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class Application extends javafx.application.Application {
|
public class Application extends javafx.application.Application {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(Application.class);
|
private static final Logger logger = LoggerFactory.getLogger(Application.class);
|
||||||
|
@Getter
|
||||||
|
private static Stage primaryStage;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws Exception {
|
public void init() {
|
||||||
logger.info("application init ...");
|
logger.info("application init ...");
|
||||||
// 初始化客户端配置
|
// 初始化客户端配置
|
||||||
CustomConfig.init();
|
ConfigManager.load();
|
||||||
|
|
||||||
|
// http请求工具初始化
|
||||||
|
HttpConfig httpConfig = new HttpConfig();
|
||||||
|
// 加载代理设置
|
||||||
|
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
|
@Override
|
||||||
@ -37,41 +60,36 @@ public class Application extends javafx.application.Application {
|
|||||||
|
|
||||||
logger.info("application start ...");
|
logger.info("application start ...");
|
||||||
|
|
||||||
|
Application.primaryStage = primaryStage;
|
||||||
|
|
||||||
|
Context.setApplication(this);
|
||||||
|
|
||||||
// 初始化弹窗工具
|
// 初始化弹窗工具
|
||||||
AlertUtil.initOwner(primaryStage);
|
AlertUtil.initOwner(primaryStage);
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 全局异常处理
|
// 全局异常处理
|
||||||
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
|
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
|
||||||
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
|
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
|
||||||
|
|
||||||
|
// i18n
|
||||||
|
Context.setLanguage(ConfigManager.language());
|
||||||
|
|
||||||
|
// 主题样式
|
||||||
|
Application.setUserAgentStylesheet(ConfigManager.theme().getUserAgentStylesheet());
|
||||||
|
|
||||||
// 启动主界面
|
// 启动主界面
|
||||||
try {
|
primaryStage.getIcons().add(new Image(Objects.requireNonNull(this.getClass().getResourceAsStream("/assets/logo.png"))));
|
||||||
FXMLLoader loader = FxmlUtil.load("root-view");
|
|
||||||
loader.setControllerFactory(c -> new MainController(primaryStage));
|
|
||||||
Parent root = loader.load();//底层面板
|
|
||||||
|
|
||||||
Scene scene = new Scene(root);
|
|
||||||
scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm());
|
|
||||||
scene.setFill(Color.TRANSPARENT);
|
|
||||||
|
|
||||||
primaryStage.setScene(scene);
|
|
||||||
primaryStage.initStyle(StageStyle.TRANSPARENT);
|
primaryStage.initStyle(StageStyle.TRANSPARENT);
|
||||||
primaryStage.setTitle(String.format("%s v%s", AppConstant.APP_TITLE, AppConstant.APP_VERSION));
|
primaryStage.setTitle(String.format("%s v%s", Constants.APP_TITLE, Constants.APP_VERSION));
|
||||||
|
Scene scene = Context.initScene();
|
||||||
|
primaryStage.setScene(scene);
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
|
|
||||||
MainController controller = loader.getController();
|
// 静默启动
|
||||||
controller.setApplication(this);
|
if (ConfigManager.silentStartup()) {
|
||||||
} catch (Throwable t) {
|
Platform.setImplicitExit(false);
|
||||||
showErrorDialog(Thread.currentThread(), t);
|
primaryStage.hide();
|
||||||
|
SystemTrayManager.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("application start over ...");
|
logger.info("application start over ...");
|
||||||
@ -79,15 +97,20 @@ public class Application extends javafx.application.Application {
|
|||||||
|
|
||||||
private void showErrorDialog(Thread t, Throwable e) {
|
private void showErrorDialog(Thread t, Throwable e) {
|
||||||
logger.error("", e);
|
logger.error("", e);
|
||||||
AlertUtil.exceptionAlert(new Exception(e)).show();
|
Platform.runLater(() -> AlertUtil.exception(new Exception(e)).show());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() throws Exception {
|
public void stop() {
|
||||||
logger.info("application stop ...");
|
logger.info("application stop ...");
|
||||||
|
// 关闭所有命令
|
||||||
|
ProcessesUtil.destroyAll();
|
||||||
|
// 保存应用数据
|
||||||
|
ConfigManager.save();
|
||||||
// 停止所有线程
|
// 停止所有线程
|
||||||
ThreadPoolManager.getInstance().shutdown();
|
ThreadPoolManager.getInstance().shutdown();
|
||||||
// 保存应用数据
|
// 关闭主界面
|
||||||
CustomConfig.store();
|
Platform.exit();
|
||||||
|
System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
57
src/main/java/cn/octopusyan/alistgui/base/BaseBuilder.java
Normal file
57
src/main/java/cn/octopusyan/alistgui/base/BaseBuilder.java
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package cn.octopusyan.alistgui.base;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.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() {
|
||||||
|
if (dialog.isShowing())
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,26 +1,29 @@
|
|||||||
package cn.octopusyan.alistgui.base;
|
package cn.octopusyan.alistgui.base;
|
||||||
|
|
||||||
import cn.octopusyan.alistgui.config.AppConstant;
|
import cn.octopusyan.alistgui.Application;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.config.I18n;
|
||||||
import cn.octopusyan.alistgui.util.FxmlUtil;
|
import cn.octopusyan.alistgui.util.FxmlUtil;
|
||||||
import javafx.application.Application;
|
import cn.octopusyan.alistgui.util.WindowsUtil;
|
||||||
import javafx.application.Platform;
|
import javafx.fxml.FXML;
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.control.Labeled;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.stage.Modality;
|
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import javafx.stage.Window;
|
import lombok.Getter;
|
||||||
|
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.IOException;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.ParameterizedType;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Objects;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,86 +31,63 @@ import java.util.ResourceBundle;
|
|||||||
*
|
*
|
||||||
* @author octopus_yan@foxmail.com
|
* @author octopus_yan@foxmail.com
|
||||||
*/
|
*/
|
||||||
public abstract class BaseController<P extends Pane> implements Initializable {
|
public abstract class BaseController<VM extends BaseViewModel> implements Initializable {
|
||||||
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
private Application application;
|
@Getter
|
||||||
private final Stage primaryStage;
|
protected final VM viewModel;
|
||||||
|
|
||||||
private double xOffSet = 0, yOffSet = 0;
|
public BaseController() {
|
||||||
|
//初始化时保存当前Controller实例
|
||||||
|
Context.getControllers().put(this.getClass().getSimpleName(), this);
|
||||||
|
|
||||||
protected BaseController(Stage primaryStage) {
|
// view model
|
||||||
this.primaryStage = primaryStage;
|
VM vm = null;
|
||||||
}
|
Type superclass = getClass().getGenericSuperclass();
|
||||||
|
if (superclass instanceof ParameterizedType type) {
|
||||||
public void jumpTo(BaseController<P> controller) throws IOException {
|
Class<VM> clazz = (Class<VM>) type.getActualTypeArguments()[0];
|
||||||
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 {
|
try {
|
||||||
FXMLLoader load = FxmlUtil.load(clazz.getDeclaredConstructor().newInstance().getRootFxml());
|
vm = clazz.getDeclaredConstructor().newInstance();
|
||||||
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) {
|
} catch (Exception e) {
|
||||||
logger.error("", e);
|
logger.error("", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModel = vm;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国际化绑定
|
||||||
|
*/
|
||||||
|
private void bindI18n() {
|
||||||
|
// i18n 绑定
|
||||||
|
try {
|
||||||
|
for (Field field : getAllField(this.getClass())) {
|
||||||
|
I18n i18n = field.getAnnotation(I18n.class);
|
||||||
|
if (i18n != null && StringUtils.isNoneEmpty(i18n.key())) {
|
||||||
|
switch (field.get(this)) {
|
||||||
|
case Labeled labeled -> labeled.textProperty().bind(Context.getLanguageBinding(i18n.key()));
|
||||||
|
case Tab tab -> tab.textProperty().bind(Context.getLanguageBinding(i18n.key()));
|
||||||
|
case MenuItem mi -> mi.textProperty().bind(Context.getLanguageBinding(i18n.key()));
|
||||||
|
default -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
logger.error("获取属性失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||||
// 全局窗口拖拽
|
// 全局窗口拖拽
|
||||||
if (dragWindow()) {
|
if (dragWindow() && getRootPanel() != null) {
|
||||||
// 窗口拖拽
|
// 窗口拖拽
|
||||||
getRootPanel().setOnMousePressed(event -> {
|
WindowsUtil.bindDragged(getRootPanel());
|
||||||
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) -> {
|
bindI18n();
|
||||||
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();
|
initData();
|
||||||
@ -118,57 +98,36 @@ public abstract class BaseController<P extends Pane> implements Initializable {
|
|||||||
// 初始化视图事件
|
// 初始化视图事件
|
||||||
initViewAction();
|
initViewAction();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setApplication(Application application) {
|
|
||||||
this.application = application;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Application getApplication() {
|
|
||||||
return application;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Stage getPrimaryStage() {
|
|
||||||
return primaryStage;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 窗口拖拽设置
|
* 窗口拖拽设置
|
||||||
*
|
*
|
||||||
* @return 是否启用
|
* @return 是否启用
|
||||||
*/
|
*/
|
||||||
public abstract boolean dragWindow();
|
public boolean dragWindow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取根布局
|
* 获取根布局
|
||||||
*
|
*
|
||||||
* @return 根布局对象
|
* @return 根布局对象
|
||||||
*/
|
*/
|
||||||
public abstract P getRootPanel();
|
public abstract Pane getRootPanel();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取根布局
|
* 获取根布局
|
||||||
* <p> 搭配 <code>FxmlUtil.load</code> 使用
|
* <p> 搭配 {@link FxmlUtil#load(String)} 使用
|
||||||
*
|
*
|
||||||
* @return 根布局对象
|
* @return 根布局对象
|
||||||
* @see FxmlUtil#load(String)
|
|
||||||
*/
|
*/
|
||||||
protected String getRootFxml() {
|
protected String getRootFxml() {
|
||||||
System.out.println(getClass().getSimpleName());
|
System.out.println(getClass().getSimpleName());
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Window getWindow() {
|
protected Stage getWindow() {
|
||||||
return getRootPanel().getScene().getWindow();
|
return Application.getPrimaryStage();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* App版本信息标签
|
|
||||||
*/
|
|
||||||
public Label getAppVersionLabel() {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,18 +145,15 @@ public abstract class BaseController<P extends Pane> implements Initializable {
|
|||||||
*/
|
*/
|
||||||
public abstract void initViewAction();
|
public abstract void initViewAction();
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭窗口
|
private static List<Field> getAllField(Class<?> class1) {
|
||||||
*/
|
List<Field> list = new ArrayList<>();
|
||||||
public void onDestroy() {
|
while (class1 != Object.class) {
|
||||||
Stage stage = (Stage) getWindow();
|
list.addAll(Arrays.stream(class1.getDeclaredFields()).toList());
|
||||||
stage.hide();
|
//获取父类
|
||||||
stage.close();
|
class1 = class1.getSuperclass();
|
||||||
try {
|
|
||||||
Thread.sleep(1000);
|
|
||||||
Platform.exit();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
logger.error("", e);
|
|
||||||
}
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
58
src/main/java/cn/octopusyan/alistgui/base/BaseTask.java
Normal file
58
src/main/java/cn/octopusyan/alistgui/base/BaseTask.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package cn.octopusyan.alistgui.base;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public abstract class BaseTask extends Task<Void> {
|
||||||
|
private final ThreadPoolManager Executor = ThreadPoolManager.getInstance();
|
||||||
|
protected Listener listener;
|
||||||
|
@Getter
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
protected BaseTask(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void call() throws Exception {
|
||||||
|
if (listener != null) listener.onStart();
|
||||||
|
task();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void task() throws Exception;
|
||||||
|
|
||||||
|
public void onListen(Listener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
if (this.listener == null) return;
|
||||||
|
|
||||||
|
setOnRunning(_ -> listener.onRunning());
|
||||||
|
setOnCancelled(_ -> listener.onCancelled());
|
||||||
|
setOnFailed(_ -> listener.onFailed(getException()));
|
||||||
|
setOnSucceeded(_ -> listener.onSucceeded());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() {
|
||||||
|
Executor.execute(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
default void onStart() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onRunning() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onCancelled() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onFailed(Throwable throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main/java/cn/octopusyan/alistgui/base/BaseViewModel.java
Normal file
13
src/main/java/cn/octopusyan/alistgui/base/BaseViewModel.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package cn.octopusyan.alistgui.base;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View Model
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public abstract class BaseViewModel {
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package cn.octopusyan.alistgui.config;
|
|
||||||
|
|
||||||
import cn.octopusyan.alistgui.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";
|
|
||||||
}
|
|
||||||
29
src/main/java/cn/octopusyan/alistgui/config/Constants.java
Normal file
29
src/main/java/cn/octopusyan/alistgui/config/Constants.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package cn.octopusyan.alistgui.config;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.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 = System.getProperty("java.io.tmpdir") + APP_NAME;
|
||||||
|
|
||||||
|
public static final String ALIST_FILE = STR."\{BIN_DIR_PATH}\{File.separator}alist.exe";
|
||||||
|
public static final String CONFIG_DIR_PATH = STR."\{DATA_DIR_PATH}\{File.separator}config";
|
||||||
|
public static final String GUI_CONFIG_PATH = STR."\{CONFIG_DIR_PATH}\{File.separator}gui.yaml";
|
||||||
|
public static final String BAK_FILE_PATH = STR."\{Constants.TMP_DIR_PATH}\{File.separator}bak";
|
||||||
|
|
||||||
|
public static final String REG_AUTO_RUN = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||||
|
public static final String APP_EXE = STR."\{DATA_DIR_PATH}\{File.separator}\{APP_NAME}.exe";
|
||||||
|
}
|
||||||
207
src/main/java/cn/octopusyan/alistgui/config/Context.java
Normal file
207
src/main/java/cn/octopusyan/alistgui/config/Context.java
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package cn.octopusyan.alistgui.config;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.Application;
|
||||||
|
import cn.octopusyan.alistgui.base.BaseController;
|
||||||
|
import cn.octopusyan.alistgui.controller.AboutController;
|
||||||
|
import cn.octopusyan.alistgui.controller.MainController;
|
||||||
|
import cn.octopusyan.alistgui.controller.RootController;
|
||||||
|
import cn.octopusyan.alistgui.controller.SetupController;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConsoleLog;
|
||||||
|
import cn.octopusyan.alistgui.util.FxmlUtil;
|
||||||
|
import cn.octopusyan.alistgui.util.ProcessesUtil;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
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.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test contect
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class Context {
|
||||||
|
@Getter
|
||||||
|
private static Application application;
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Context.class);
|
||||||
|
private static Scene scene;
|
||||||
|
private static final IntegerProperty currentViewIndex = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制器集合
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private static final Map<String, BaseController<?>> controllers = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 默认语言文件 Base Name
|
||||||
|
*/
|
||||||
|
private static final String LANGUAGE_RESOURCE_NAME = "language/language";
|
||||||
|
/**
|
||||||
|
* 语言资源工厂
|
||||||
|
*/
|
||||||
|
private static final ObservableResourceBundleFactory LANGUAGE_RESOURCE_FACTORY = new ObservableResourceBundleFactory();
|
||||||
|
/**
|
||||||
|
* 支持的语言集合,应与语言资源文件同步手动更新
|
||||||
|
*/
|
||||||
|
public static final List<Locale> SUPPORT_LANGUAGE_LIST = Arrays.asList(Locale.SIMPLIFIED_CHINESE, Locale.ENGLISH);
|
||||||
|
/**
|
||||||
|
* 记录当前所选时区
|
||||||
|
*/
|
||||||
|
private static final ObjectProperty<Locale> currentLocale = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
|
||||||
|
private Context() {
|
||||||
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取控制工厂
|
||||||
|
public static Callback<Class<?>, Object> getControlFactory() {
|
||||||
|
return type -> {
|
||||||
|
try {
|
||||||
|
return switch (type.getDeclaredConstructor().newInstance()) {
|
||||||
|
case RootController root -> root;
|
||||||
|
case MainController main -> main;
|
||||||
|
case SetupController setup -> setup;
|
||||||
|
case AboutController about -> about;
|
||||||
|
default -> throw new IllegalStateException(STR."Unexpected value: \{type}");
|
||||||
|
};
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setApplication(Application application) {
|
||||||
|
Context.application = application;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前所选时区属性
|
||||||
|
public static ObjectProperty<Locale> currentLocaleProperty() {
|
||||||
|
return currentLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置当前所选时区
|
||||||
|
public static void setCurrentLocale(Locale locale) {
|
||||||
|
currentLocaleProperty().set(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更换语言的组件使用此方法初始化自己的值,调用 {@link Context#setLanguage(Locale)} 来更新界面语言
|
||||||
|
*
|
||||||
|
* @return 当前界面语言
|
||||||
|
*/
|
||||||
|
// 获取当前界面语言
|
||||||
|
public static Locale getCurrentLocale() {
|
||||||
|
return currentLocaleProperty().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新界面语言
|
||||||
|
*
|
||||||
|
* @param locale 区域
|
||||||
|
*/
|
||||||
|
// 更新界面语言
|
||||||
|
public static void setLanguage(Locale locale) {
|
||||||
|
setCurrentLocale(locale);
|
||||||
|
Locale.setDefault(locale);
|
||||||
|
ConfigManager.language(locale);
|
||||||
|
LANGUAGE_RESOURCE_FACTORY.setResourceBundle(ResourceBundle.getBundle(LANGUAGE_RESOURCE_NAME, locale));
|
||||||
|
|
||||||
|
log.info("language changed to {}", locale);
|
||||||
|
ConsoleLog.info(STR."language changed to \{locale}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定标识的字符串绑定
|
||||||
|
*
|
||||||
|
* @param key 标识
|
||||||
|
* @return 对应该标识的字符串属性绑定
|
||||||
|
*/
|
||||||
|
// 获取指定标识的字符串绑定
|
||||||
|
public static StringBinding getLanguageBinding(String key) {
|
||||||
|
return LANGUAGE_RESOURCE_FACTORY.getStringBinding(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取语言资源属性
|
||||||
|
*/
|
||||||
|
public static ObjectProperty<ResourceBundle> getLanguageResource() {
|
||||||
|
return LANGUAGE_RESOURCE_FACTORY.getResourceBundleProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有此类所在路径决定相对路径
|
||||||
|
*
|
||||||
|
* @param path 资源文件相对路径
|
||||||
|
* @return 资源文件路径
|
||||||
|
*/
|
||||||
|
// 加载资源文件
|
||||||
|
public static URL load(String path) {
|
||||||
|
return Context.class.getResource(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化场景
|
||||||
|
*
|
||||||
|
* @return Scene
|
||||||
|
*/
|
||||||
|
public static Scene initScene() {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = FxmlUtil.load("root-view");
|
||||||
|
//底层面板
|
||||||
|
Pane root = loader.load();
|
||||||
|
Optional.ofNullable(scene).ifPresentOrElse(
|
||||||
|
s -> s.setRoot(root),
|
||||||
|
() -> {
|
||||||
|
scene = new Scene(root, root.getPrefWidth() + 20, root.getPrefHeight() + 20, Color.TRANSPARENT);
|
||||||
|
URL resource = Objects.requireNonNull(Context.class.getResource("/css/root-view.css"));
|
||||||
|
scene.getStylesheets().addAll(resource.toExternalForm());
|
||||||
|
scene.setFill(Color.TRANSPARENT);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error("loadScene error", e);
|
||||||
|
}
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int currentViewIndex() {
|
||||||
|
return currentViewIndex.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IntegerProperty currentViewIndexProperty() {
|
||||||
|
return currentViewIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,110 +0,0 @@
|
|||||||
package cn.octopusyan.alistgui.config;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Objects;
|
|
||||||
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 void init() {
|
|
||||||
FileReader reader = null;
|
|
||||||
try {
|
|
||||||
File file = new File(AppConstant.CUSTOM_CONFIG_PATH);
|
|
||||||
if (!file.exists()) {
|
|
||||||
// 创建配置文件
|
|
||||||
if (!file.getParentFile().exists()) {
|
|
||||||
FileUtils.createParentDirectories(file);
|
|
||||||
}
|
|
||||||
boolean newFile = file.createNewFile();
|
|
||||||
// 保存配置
|
|
||||||
store();
|
|
||||||
} else {
|
|
||||||
reader = new FileReader(file);
|
|
||||||
properties.load(reader);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("读取配置文件失败", e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
if (reader != null) {
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("关闭配置文件流", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否配置代理
|
|
||||||
*/
|
|
||||||
public static boolean hasProxy() {
|
|
||||||
String host = proxyHost();
|
|
||||||
Integer port = proxyPort();
|
|
||||||
|
|
||||||
return StringUtils.isNoneBlank(host) && Objects.nonNull(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 10809;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 代理端口
|
|
||||||
*/
|
|
||||||
public static void proxyPort(int port) {
|
|
||||||
properties.setProperty(PROXY_PORT_KEY, String.valueOf(port));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存配置
|
|
||||||
*/
|
|
||||||
public static void store() {
|
|
||||||
// 生成配置文件
|
|
||||||
try {
|
|
||||||
properties.store(new PrintStream(AppConstant.CUSTOM_CONFIG_PATH), String.valueOf(StandardCharsets.UTF_8));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("保存客户端配置失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
src/main/java/cn/octopusyan/alistgui/config/I18n.java
Normal file
15
src/main/java/cn/octopusyan/alistgui/config/I18n.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package cn.octopusyan.alistgui.config;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示文本绑定
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.FIELD})//用此注解用在属性上。
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface I18n {
|
||||||
|
String key() default "";
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package cn.octopusyan.alistgui.config;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多国语言属性绑定
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class ObservableResourceBundleFactory {
|
||||||
|
|
||||||
|
private final ObjectProperty<ResourceBundle> resourceBundleProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
public ResourceBundle getResourceBundle() {
|
||||||
|
return getResourceBundleProperty().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceBundle(ResourceBundle resourceBundle) {
|
||||||
|
getResourceBundleProperty().set(resourceBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBinding getStringBinding(String key) {
|
||||||
|
return Bindings.createStringBinding(() -> getResourceBundle().getString(key), resourceBundleProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package cn.octopusyan.alistgui.controller;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseController;
|
||||||
|
import cn.octopusyan.alistgui.config.I18n;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.alistgui.view.alert.AlertUtil;
|
||||||
|
import cn.octopusyan.alistgui.viewModel.AboutViewModule;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关于
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class AboutController extends BaseController<AboutViewModule> {
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
public VBox aboutView;
|
||||||
|
|
||||||
|
public Label aListVersion;
|
||||||
|
|
||||||
|
@I18n(key = "about.alist.version")
|
||||||
|
public Label aListVersionLabel;
|
||||||
|
|
||||||
|
@I18n(key = "about.app.version")
|
||||||
|
public Label appVersionLabel;
|
||||||
|
|
||||||
|
@I18n(key = "about.app.update")
|
||||||
|
public Button checkAppVersion;
|
||||||
|
|
||||||
|
@I18n(key = "about.alist.update")
|
||||||
|
public Button checkAListVersion;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VBox getRootPanel() {
|
||||||
|
return aboutView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewStyle() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
aListVersion.textProperty().bindBidirectional(viewModel.aListVersionProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAListUpdate() {
|
||||||
|
viewModel.checkUpdate(ConfigManager.aList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkGuiUpdate() {
|
||||||
|
// TODO 检查 gui 版本
|
||||||
|
AlertUtil.info("待开发。。。").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,102 +1,143 @@
|
|||||||
package cn.octopusyan.alistgui.controller;
|
package cn.octopusyan.alistgui.controller;
|
||||||
|
|
||||||
import cn.octopusyan.alistgui.base.BaseController;
|
import cn.octopusyan.alistgui.base.BaseController;
|
||||||
import javafx.css.PseudoClass;
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
import javafx.fxml.FXML;
|
import cn.octopusyan.alistgui.config.I18n;
|
||||||
import javafx.fxml.Initializable;
|
import cn.octopusyan.alistgui.manager.AListManager;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConsoleLog;
|
||||||
|
import cn.octopusyan.alistgui.util.FxmlUtil;
|
||||||
|
import cn.octopusyan.alistgui.viewModel.MainViewModel;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.TabPane;
|
import javafx.scene.control.MenuButton;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.control.MenuItem;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.control.ScrollPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.Stage;
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主页面控制器
|
* 主界面控制器
|
||||||
*
|
*
|
||||||
* @author octopus_yan@foxmail.com
|
* @author octopus_yan
|
||||||
*/
|
*/
|
||||||
public class MainController extends BaseController<VBox> implements Initializable {
|
public class MainController extends BaseController<MainViewModel> {
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
private double xOffset;
|
public VBox mainView;
|
||||||
private double yOffset;
|
public VBox logArea;
|
||||||
|
public ScrollPane logAreaSp;
|
||||||
|
|
||||||
// 布局
|
@I18n(key = "main.status.label-stop")
|
||||||
@FXML
|
public Button statusLabel;
|
||||||
public VBox rootPane;
|
|
||||||
@FXML
|
|
||||||
public HBox windowHeader;
|
|
||||||
@FXML
|
|
||||||
public Button alwaysOnTopIcon;
|
|
||||||
@FXML
|
|
||||||
public Button minimizeIcon;
|
|
||||||
@FXML
|
|
||||||
public Button closeIcon;
|
|
||||||
|
|
||||||
// 界面
|
@I18n(key = "main.control.start")
|
||||||
@FXML
|
public Button startButton;
|
||||||
public TabPane tabPane;
|
|
||||||
|
|
||||||
public MainController(Stage primaryStage) {
|
@I18n(key = "main.control.password")
|
||||||
super(primaryStage);
|
public Button passwordButton;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@I18n(key = "main.control.restart")
|
||||||
* 窗口拖拽设置
|
public Button restartButton;
|
||||||
*
|
|
||||||
* @return 是否启用
|
@I18n(key = "main.control.more")
|
||||||
*/
|
public MenuButton moreButton;
|
||||||
@Override
|
|
||||||
public boolean dragWindow() {
|
@I18n(key = "main.more.browser")
|
||||||
return false;
|
public MenuItem browserButton;
|
||||||
}
|
|
||||||
|
@I18n(key = "main.more.open-config")
|
||||||
|
public MenuItem configButton;
|
||||||
|
|
||||||
|
@I18n(key = "main.more.open-log")
|
||||||
|
public MenuItem logButton;
|
||||||
|
|
||||||
|
private PasswordController controller;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取根布局
|
|
||||||
*
|
|
||||||
* @return 根布局对象
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public VBox getRootPanel() {
|
public VBox getRootPanel() {
|
||||||
return rootPane;
|
return mainView;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化数据
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void initData() {
|
public void initData() {
|
||||||
|
ConsoleLog.init(logAreaSp, logArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 视图样式
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void initViewStyle() {
|
public void initViewStyle() {
|
||||||
|
// 运行状态监听
|
||||||
|
viewModel.runningProperty().addListener((_, _, running) -> {
|
||||||
|
resetStatus(running);
|
||||||
|
browserButton.disableProperty().set(!running);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// start button
|
||||||
|
public void start() {
|
||||||
|
if (AListManager.isRunning()) {
|
||||||
|
AListManager.stop();
|
||||||
|
} else {
|
||||||
|
AListManager.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// password button
|
||||||
|
public void adminPassword() throws IOException {
|
||||||
|
if (controller == null) {
|
||||||
|
FXMLLoader load = FxmlUtil.load("admin-panel");
|
||||||
|
load.load();
|
||||||
|
controller = load.getController();
|
||||||
|
}
|
||||||
|
controller.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// restart button
|
||||||
|
public void restart() {
|
||||||
|
AListManager.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
// more button
|
||||||
|
|
||||||
|
public void openInBrowser() {
|
||||||
|
AListManager.openScheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openLogFolder() {
|
||||||
|
AListManager.openLogFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openConfig() {
|
||||||
|
AListManager.openConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 视图事件
|
* 根据运行状态改变按钮样式
|
||||||
|
*
|
||||||
|
* @param running 运行状态
|
||||||
*/
|
*/
|
||||||
@Override
|
private void resetStatus(boolean running) {
|
||||||
public void initViewAction() {
|
String removeStyle = running ? "success" : "danger";
|
||||||
closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> onDestroy());
|
String addStyle = running ? "danger" : "success";
|
||||||
minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> ((Stage) rootPane.getScene().getWindow()).setIconified(true));
|
StringBinding button = Context.getLanguageBinding(STR."main.control.\{running ? "stop" : "start"}");
|
||||||
alwaysOnTopIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
StringBinding status = Context.getLanguageBinding(STR."main.status.label-\{running ? "running" : "stop"}");
|
||||||
boolean newVal = !getPrimaryStage().isAlwaysOnTop();
|
|
||||||
alwaysOnTopIcon.pseudoClassStateChanged(PseudoClass.getPseudoClass("always-on-top"), newVal);
|
|
||||||
getPrimaryStage().setAlwaysOnTop(newVal);
|
|
||||||
});
|
|
||||||
|
|
||||||
windowHeader.setOnMousePressed(event -> {
|
Platform.runLater(() -> {
|
||||||
xOffset = getPrimaryStage().getX() - event.getScreenX();
|
startButton.getStyleClass().remove(removeStyle);
|
||||||
yOffset = getPrimaryStage().getY() - event.getScreenY();
|
startButton.getStyleClass().add(addStyle);
|
||||||
});
|
startButton.textProperty().bind(button);
|
||||||
windowHeader.setOnMouseDragged(event -> {
|
|
||||||
getPrimaryStage().setX(event.getScreenX() + xOffset);
|
statusLabel.getStyleClass().remove(addStyle);
|
||||||
getPrimaryStage().setY(event.getScreenY() + yOffset);
|
statusLabel.getStyleClass().add(removeStyle);
|
||||||
|
statusLabel.textProperty().bind(status);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,129 @@
|
|||||||
|
package cn.octopusyan.alistgui.controller;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Popover;
|
||||||
|
import cn.hutool.core.swing.clipboard.ClipboardUtil;
|
||||||
|
import cn.octopusyan.alistgui.base.BaseController;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.config.I18n;
|
||||||
|
import cn.octopusyan.alistgui.manager.AListManager;
|
||||||
|
import cn.octopusyan.alistgui.viewModel.AdminPanelViewModel;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.PasswordField;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员密码
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class PasswordController extends BaseController<AdminPanelViewModel> {
|
||||||
|
public AnchorPane adminPanel;
|
||||||
|
|
||||||
|
@I18n(key = "admin.pwd.toptip")
|
||||||
|
public Label toptip;
|
||||||
|
@I18n(key = "admin.pwd.user-field")
|
||||||
|
public Label usernameLabel;
|
||||||
|
public TextField usernameField;
|
||||||
|
@FXML
|
||||||
|
public Button copyUsername;
|
||||||
|
@I18n(key = "admin.pwd.pwd-field")
|
||||||
|
public Label passwordLabel;
|
||||||
|
public PasswordField passwordField;
|
||||||
|
public Button refreshPassword;
|
||||||
|
public Button savePassword;
|
||||||
|
public Button copyPassword;
|
||||||
|
|
||||||
|
private RootController root;
|
||||||
|
|
||||||
|
private final Popover pop = new Popover(new Text(Context.getLanguageBinding("msg.alist.pwd.copy").get()));
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Pane getRootPanel() {
|
||||||
|
return adminPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
root = (RootController) Context.getControllers().get("RootController");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewStyle() {
|
||||||
|
pop.setArrowLocation(Popover.ArrowLocation.BOTTOM_CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
passwordField.textProperty().bindBidirectional(viewModel.passwordProperty());
|
||||||
|
passwordField.setOnMouseClicked(event -> {
|
||||||
|
// 点击密码框时,设置为可修改状态
|
||||||
|
passwordField.setEditable(true);
|
||||||
|
refreshPassword.setVisible(true);
|
||||||
|
refreshPassword.setManaged(true);
|
||||||
|
});
|
||||||
|
ChangeListener<Boolean> changeListener = (_, _, focused) -> {
|
||||||
|
if (!focused && !refreshPassword.isFocused()
|
||||||
|
&& !copyPassword.isFocused()
|
||||||
|
&& StringUtils.equals(passwordField.getText(), AListManager.passwordProperty().get())) {
|
||||||
|
// 当密码栏失去焦点,如果密码未变更,设置为不可用状态
|
||||||
|
passwordField.setEditable(false);
|
||||||
|
refreshPassword.setVisible(false);
|
||||||
|
refreshPassword.setManaged(false);
|
||||||
|
savePassword.setVisible(false);
|
||||||
|
savePassword.setManaged(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
passwordField.focusedProperty().addListener(changeListener);
|
||||||
|
refreshPassword.focusedProperty().addListener(changeListener);
|
||||||
|
savePassword.focusedProperty().addListener(changeListener);
|
||||||
|
copyPassword.focusedProperty().addListener(changeListener);
|
||||||
|
// 监听密码修改,展示保存按钮
|
||||||
|
passwordField.textProperty().addListener((_, _, newValue) -> {
|
||||||
|
boolean equals = StringUtils.equals(newValue, AListManager.passwordProperty().get());
|
||||||
|
savePassword.setVisible(!equals);
|
||||||
|
savePassword.setManaged(!equals);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
root.showModal(getRootPanel(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
passwordField.setText(AListManager.passwordProperty().get());
|
||||||
|
root.hideModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void copyUsername() {
|
||||||
|
usernameField.copy();
|
||||||
|
pop.show(copyUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void savePassword(ActionEvent event) {
|
||||||
|
Object source = event.getSource();
|
||||||
|
if (refreshPassword.equals(source)) {
|
||||||
|
AListManager.resetPassword();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AListManager.resetPassword(passwordField.getText());
|
||||||
|
savePassword.setVisible(false);
|
||||||
|
savePassword.setManaged(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void copyPassword() {
|
||||||
|
ClipboardUtil.setStr(AListManager.password());
|
||||||
|
pop.show(copyPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
package cn.octopusyan.alistgui.controller;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.ModalPane;
|
||||||
|
import cn.octopusyan.alistgui.base.BaseController;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.config.I18n;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.alistgui.manager.SystemTrayManager;
|
||||||
|
import cn.octopusyan.alistgui.util.WindowsUtil;
|
||||||
|
import cn.octopusyan.alistgui.viewModel.RootViewModel;
|
||||||
|
import com.gluonhq.emoji.EmojiData;
|
||||||
|
import com.gluonhq.emoji.util.EmojiImageUtils;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.css.PseudoClass;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TabPane;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root 页面控制器
|
||||||
|
*
|
||||||
|
* @author octopus_yan@foxmail.com
|
||||||
|
*/
|
||||||
|
public class RootController extends BaseController<RootViewModel> {
|
||||||
|
// 布局
|
||||||
|
public StackPane rootPane;
|
||||||
|
public HBox windowHeader;
|
||||||
|
public FontIcon alwaysOnTopIcon;
|
||||||
|
public FontIcon minimizeIcon;
|
||||||
|
public FontIcon closeIcon;
|
||||||
|
|
||||||
|
// 界面
|
||||||
|
public TabPane tabPane;
|
||||||
|
|
||||||
|
@I18n(key = "root.tab.main")
|
||||||
|
public Tab mainTab;
|
||||||
|
@I18n(key = "root.tab.setup")
|
||||||
|
public Tab setupTab;
|
||||||
|
@I18n(key = "root.tab.about")
|
||||||
|
public Tab aboutTab;
|
||||||
|
|
||||||
|
// footer
|
||||||
|
@I18n(key = "root.foot.doc")
|
||||||
|
public Button document;
|
||||||
|
@I18n(key = "root.foot.github")
|
||||||
|
public Button github;
|
||||||
|
@I18n(key = "root.foot.sponsor")
|
||||||
|
public Button sponsor;
|
||||||
|
|
||||||
|
private final ModalPane modalPane = new ModalPane();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取根布局
|
||||||
|
*
|
||||||
|
* @return 根布局对象
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public StackPane getRootPanel() {
|
||||||
|
return rootPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化数据
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
tabPane.getSelectionModel().select(viewModel.currentViewIndexProperty().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视图样式
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initViewStyle() {
|
||||||
|
// 设置图标
|
||||||
|
EmojiData.emojiFromShortName("book").ifPresent(icon -> {
|
||||||
|
ImageView book = EmojiImageUtils.emojiView(icon, 25);
|
||||||
|
document.setGraphic(book);
|
||||||
|
});
|
||||||
|
EmojiData.emojiFromShortName("cat").ifPresent(icon -> {
|
||||||
|
ImageView githubIcon = EmojiImageUtils.emojiView(icon, 25);
|
||||||
|
github.setGraphic(githubIcon);
|
||||||
|
|
||||||
|
});
|
||||||
|
EmojiData.emojiFromShortName("tropical_drink").ifPresent(icon -> {
|
||||||
|
ImageView juice = EmojiImageUtils.emojiView(icon, 25);
|
||||||
|
sponsor.setGraphic(juice);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 遮罩
|
||||||
|
getRootPanel().getChildren().add(modalPane);
|
||||||
|
modalPane.setId("modalPane");
|
||||||
|
// reset side and transition to reuse a single modal pane between different examples
|
||||||
|
modalPane.displayProperty().addListener((obs, old, val) -> {
|
||||||
|
if (!val) {
|
||||||
|
modalPane.setAlignment(Pos.CENTER);
|
||||||
|
modalPane.usePredefinedTransitionFactories(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视图事件
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> {
|
||||||
|
if (ConfigManager.closeToTray()) {
|
||||||
|
SystemTrayManager.show();
|
||||||
|
} else {
|
||||||
|
SystemTrayManager.hide();
|
||||||
|
}
|
||||||
|
Platform.setImplicitExit(!ConfigManager.closeToTray());
|
||||||
|
getWindow().close();
|
||||||
|
});
|
||||||
|
minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> getWindow().setIconified(true));
|
||||||
|
alwaysOnTopIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> {
|
||||||
|
boolean newVal = !getWindow().isAlwaysOnTop();
|
||||||
|
alwaysOnTopIcon.pseudoClassStateChanged(PseudoClass.getPseudoClass("always-on-top"), newVal);
|
||||||
|
getWindow().setAlwaysOnTop(newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
WindowsUtil.bindDragged(windowHeader);
|
||||||
|
|
||||||
|
viewModel.currentViewIndexProperty().bind(tabPane.getSelectionModel().selectedIndexProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openDocument() {
|
||||||
|
String locale = Context.getCurrentLocale().equals(Locale.ENGLISH) ? "" : "zh/";
|
||||||
|
Context.openUrl(STR."https://alist.nn.ci/\{locale}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openGithub() {
|
||||||
|
Context.openUrl("https://github.com/alist-org/alist");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showModal(Node node, boolean persistent) {
|
||||||
|
modalPane.show(node);
|
||||||
|
modalPane.setPersistent(persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideModal() {
|
||||||
|
modalPane.hide(false);
|
||||||
|
modalPane.setPersistent(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,105 @@
|
|||||||
|
package cn.octopusyan.alistgui.controller;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Theme;
|
||||||
|
import cn.octopusyan.alistgui.base.BaseController;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.config.I18n;
|
||||||
|
import cn.octopusyan.alistgui.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.alistgui.viewModel.SetupViewModel;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置页面控制器
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class SetupController extends BaseController<SetupViewModel> implements Initializable {
|
||||||
|
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public VBox setupView;
|
||||||
|
@I18n(key = "setup.auto-start.label")
|
||||||
|
public CheckBox autoStartCheckBox;
|
||||||
|
@I18n(key = "setup.silent-startup.label")
|
||||||
|
public CheckBox silentStartupCheckBox;
|
||||||
|
@I18n(key = "setup.close-to-tray.label")
|
||||||
|
public CheckBox closeToTrayCheckBox;
|
||||||
|
public ComboBox<Locale> languageComboBox;
|
||||||
|
public ComboBox<Theme> themeComboBox;
|
||||||
|
public ComboBox<ProxySetup> proxySetupComboBox;
|
||||||
|
public Pane proxySetupPane;
|
||||||
|
@I18n(key = "setup.proxy.test")
|
||||||
|
public Button proxyCheck;
|
||||||
|
public TextField proxyHost;
|
||||||
|
public TextField proxyPort;
|
||||||
|
@I18n(key = "setup.proxy.host")
|
||||||
|
public Label hostLabel;
|
||||||
|
@I18n(key = "setup.proxy.port")
|
||||||
|
public Label portLabel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VBox getRootPanel() {
|
||||||
|
return setupView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initData() {
|
||||||
|
languageComboBox.setItems(FXCollections.observableList(Context.SUPPORT_LANGUAGE_LIST));
|
||||||
|
themeComboBox.setItems(FXCollections.observableList(ConfigManager.THEME_LIST));
|
||||||
|
proxySetupComboBox.setItems(FXCollections.observableList(List.of(ProxySetup.values())));
|
||||||
|
|
||||||
|
themeComboBox.setConverter(new StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(Theme object) {
|
||||||
|
return object.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Theme fromString(String string) {
|
||||||
|
return ConfigManager.THEME_MAP.get(string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewStyle() {
|
||||||
|
proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
|
||||||
|
proxySetupPane.setVisible(ProxySetup.MANUAL.equals(newValue));
|
||||||
|
proxyCheck.setVisible(!ProxySetup.NO_PROXY.equals(newValue));
|
||||||
|
});
|
||||||
|
|
||||||
|
languageComboBox.getSelectionModel().select(ConfigManager.language());
|
||||||
|
themeComboBox.getSelectionModel().select(ConfigManager.theme());
|
||||||
|
proxySetupComboBox.getSelectionModel().select(ConfigManager.proxySetup());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initViewAction() {
|
||||||
|
//
|
||||||
|
autoStartCheckBox.selectedProperty().bindBidirectional(viewModel.autoStartProperty());
|
||||||
|
silentStartupCheckBox.selectedProperty().bindBidirectional(viewModel.silentStartupProperty());
|
||||||
|
closeToTrayCheckBox.selectedProperty().bindBidirectional(viewModel.closeToTrayProperty());
|
||||||
|
proxyHost.textProperty().bindBidirectional(viewModel.proxyHostProperty());
|
||||||
|
proxyPort.textProperty().bindBidirectional(viewModel.proxyPortProperty());
|
||||||
|
|
||||||
|
viewModel.languageProperty().bind(languageComboBox.getSelectionModel().selectedItemProperty());
|
||||||
|
viewModel.themeProperty().bind(themeComboBox.getSelectionModel().selectedItemProperty());
|
||||||
|
viewModel.proxySetupProperty().bind(proxySetupComboBox.getSelectionModel().selectedItemProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void proxyTest() {
|
||||||
|
viewModel.proxyTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java
Normal file
25
src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package cn.octopusyan.alistgui.enums;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
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 name;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Context.getLanguageBinding("proxy.setup.label." + getName()).getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
243
src/main/java/cn/octopusyan/alistgui/manager/AListManager.java
Normal file
243
src/main/java/cn/octopusyan/alistgui/manager/AListManager.java
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package cn.octopusyan.alistgui.manager;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.model.AListConfig;
|
||||||
|
import cn.octopusyan.alistgui.task.CheckUpdateTask;
|
||||||
|
import cn.octopusyan.alistgui.task.DownloadTask;
|
||||||
|
import cn.octopusyan.alistgui.task.listener.TaskListener;
|
||||||
|
import cn.octopusyan.alistgui.util.DownloadUtil;
|
||||||
|
import cn.octopusyan.alistgui.util.ProcessesUtil;
|
||||||
|
import cn.octopusyan.alistgui.view.alert.AlertUtil;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AList 管理
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class AListManager {
|
||||||
|
public static final String DATA_DIR = STR."\{Constants.BIN_DIR_PATH}\{File.separator}data";
|
||||||
|
public static final String LOG_DIR = STR."\{DATA_DIR}\{File.separator}log";
|
||||||
|
public static final String CONFIG_FILE = STR."\{DATA_DIR}\{File.separator}config.json";
|
||||||
|
|
||||||
|
public static final String START_COMMAND = STR."\{Constants.ALIST_FILE} server";
|
||||||
|
public static final String PWD_SET_COMMAND = STR."\{Constants.ALIST_FILE} admin set";
|
||||||
|
public static final String PWD_RANDOM_COMMAND = STR."\{Constants.ALIST_FILE} admin random";
|
||||||
|
|
||||||
|
public static final String DEFAULT_SCHEME = "0.0.0.0:5244";
|
||||||
|
public static final String PASSWORD_MSG_REG = ".*password( is)?: (.*)$";
|
||||||
|
public static AListConfig aListConfig;
|
||||||
|
|
||||||
|
public static final File configFile = new File(CONFIG_FILE);
|
||||||
|
|
||||||
|
private static final ProcessesUtil util;
|
||||||
|
private static final BooleanProperty running = new SimpleBooleanProperty(false);
|
||||||
|
private static final StringProperty password = new SimpleStringProperty("******");
|
||||||
|
private static DownloadTask downloadTask;
|
||||||
|
|
||||||
|
private static final ProcessesUtil.OnExecuteListener runningListener;
|
||||||
|
|
||||||
|
static {
|
||||||
|
util = ProcessesUtil.init(Constants.BIN_DIR_PATH);
|
||||||
|
loadConfig();
|
||||||
|
runningListener = new ProcessesUtil.OnExecuteListener() {
|
||||||
|
@Override
|
||||||
|
public void onExecute(String msg) {
|
||||||
|
if (hasConfig() && aListConfig == null) loadConfig();
|
||||||
|
|
||||||
|
if (msg.contains("start HTTP server")) {
|
||||||
|
Platform.runLater(() -> running.set(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleLog.msg(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExecuteSuccess(boolean success) {
|
||||||
|
Platform.runLater(() -> running.set(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onExecuteError(Exception e) {
|
||||||
|
Platform.runLater(() -> running.set(false));
|
||||||
|
log.error("AList error", e);
|
||||||
|
ConsoleLog.error("AList", e.getMessage());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================={ Property }====================================
|
||||||
|
|
||||||
|
private static void loadConfig() {
|
||||||
|
if (hasConfig()) {
|
||||||
|
aListConfig = ConfigManager.loadConfig(CONFIG_FILE, AListConfig.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BooleanProperty runningProperty() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRunning() {
|
||||||
|
return running.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasConfig() {
|
||||||
|
return configFile.exists() && aListConfig != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String scheme() {
|
||||||
|
return hasConfig() ?
|
||||||
|
STR."\{aListConfig.getScheme().getAddress()}:\{aListConfig.getScheme().getHttpPort()}"
|
||||||
|
: DEFAULT_SCHEME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringProperty passwordProperty() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String password() {
|
||||||
|
return password.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
//================================{ action }====================================
|
||||||
|
|
||||||
|
public static void openConfig() {
|
||||||
|
Context.openFile(new File(CONFIG_FILE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openLogFolder() {
|
||||||
|
Context.openFolder(new File(LOG_DIR));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void openScheme() {
|
||||||
|
Context.openUrl(STR."http://\{scheme()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void start() {
|
||||||
|
if (!checkAList()) return;
|
||||||
|
|
||||||
|
if (running.get() || util.isRunning()) {
|
||||||
|
ConsoleLog.warning(getText("alist.status.start.running"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleLog.info(getText("alist.status.start"));
|
||||||
|
|
||||||
|
loadConfig();
|
||||||
|
util.exec(START_COMMAND, runningListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stop() {
|
||||||
|
ConsoleLog.info(getText("alist.status.stop"));
|
||||||
|
if (!running.get()) {
|
||||||
|
ConsoleLog.warning(getText("alist.status.stop.stopped"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
util.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
static ChangeListener<Boolean> restartListener;
|
||||||
|
|
||||||
|
public static void restart() {
|
||||||
|
if (!running.get()) {
|
||||||
|
start();
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
|
|
||||||
|
restartListener = (_, _, run) -> {
|
||||||
|
if (run) return;
|
||||||
|
running.removeListener(restartListener);
|
||||||
|
start();
|
||||||
|
};
|
||||||
|
running.addListener(restartListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetPassword() {
|
||||||
|
resetPassword("");
|
||||||
|
}
|
||||||
|
|
||||||
|
static ChangeListener<Boolean> resetPasswordListener;
|
||||||
|
|
||||||
|
public static void resetPassword(String pwd) {
|
||||||
|
String command = StringUtils.isNoneEmpty(pwd) ?
|
||||||
|
STR."\{PWD_SET_COMMAND} \{pwd}" : PWD_RANDOM_COMMAND;
|
||||||
|
|
||||||
|
if (isRunning()) {
|
||||||
|
util.exec(command, ConsoleLog::msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
|
resetPasswordListener = (_, _, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
running.removeListener(resetPasswordListener);
|
||||||
|
util.exec(command, ConsoleLog::msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
running.addListener(resetPasswordListener);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================={ private }====================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO 点击开始时检查 aList 执行文件
|
||||||
|
*/
|
||||||
|
private static boolean checkAList() {
|
||||||
|
if (new File(Constants.ALIST_FILE).exists()) return true;
|
||||||
|
|
||||||
|
if (downloadTask != null && downloadTask.isRunning()) {
|
||||||
|
ConsoleLog.warning("AList Downloading ...");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var task = new CheckUpdateTask(ConfigManager.aList());
|
||||||
|
task.onListen(new TaskListener.UpgradeUpgradeListener(task) {
|
||||||
|
@Override
|
||||||
|
public void onChecked(boolean hasUpgrade, String version) {
|
||||||
|
Platform.runLater(() -> showDownload(version));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
task.execute();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showDownload(String version) {
|
||||||
|
String content = STR."""
|
||||||
|
\{getText("msg.alist.download.notfile")}
|
||||||
|
\{Context.getLanguageBinding("update.remote").get()} : \{version}
|
||||||
|
""";
|
||||||
|
downloadTask = DownloadUtil.startDownload(ConfigManager.aList(), version, () -> {
|
||||||
|
DownloadUtil.unzip(ConfigManager.aList());
|
||||||
|
Platform.runLater(() -> ConfigManager.aListVersion(version));
|
||||||
|
restart();
|
||||||
|
});
|
||||||
|
AlertUtil.confirm()
|
||||||
|
.title("Download ALst")
|
||||||
|
.header(null)
|
||||||
|
.content(content)
|
||||||
|
.show(downloadTask::execute);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getText(String code) {
|
||||||
|
return Context.getLanguageBinding(code).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tmpPassword(String pwd) {
|
||||||
|
Platform.runLater(() -> password.set(pwd));
|
||||||
|
}
|
||||||
|
}
|
||||||
287
src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
Normal file
287
src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
package cn.octopusyan.alistgui.manager;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.*;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.lang.PatternPool;
|
||||||
|
import cn.hutool.core.net.NetUtil;
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
import cn.octopusyan.alistgui.Application;
|
||||||
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
|
import cn.octopusyan.alistgui.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.alistgui.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
|
||||||
|
import cn.octopusyan.alistgui.model.GuiConfig;
|
||||||
|
import cn.octopusyan.alistgui.model.ProxyInfo;
|
||||||
|
import cn.octopusyan.alistgui.model.UpgradeConfig;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.AList;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.Gui;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import org.apache.commons.lang3.LocaleUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
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 Locale DEFAULT_LANGUAGE = Locale.SIMPLIFIED_CHINESE;
|
||||||
|
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 GuiConfig guiConfig;
|
||||||
|
|
||||||
|
static {
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load() {
|
||||||
|
guiConfig = loadConfig(Constants.GUI_CONFIG_PATH, GuiConfig.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
File parent = FileUtil.getParent(src, 1);
|
||||||
|
if (!parent.exists()) {
|
||||||
|
boolean wasSuccessful = parent.mkdirs();
|
||||||
|
objectMapper.writeValue(src, clazz.getDeclaredConstructor().newInstance());
|
||||||
|
if (!wasSuccessful)
|
||||||
|
logger.error("{} 创建失败", src.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save() {
|
||||||
|
try {
|
||||||
|
objectMapper.writeValue(new File(Constants.GUI_CONFIG_PATH), guiConfig);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("save config error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 主题 }------------------------------------------
|
||||||
|
|
||||||
|
public static String themeName() {
|
||||||
|
return guiConfig.getTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Theme theme() {
|
||||||
|
return THEME_MAP.get(themeName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void theme(Theme theme) {
|
||||||
|
Application.setUserAgentStylesheet(theme.getUserAgentStylesheet());
|
||||||
|
guiConfig.setTheme(theme.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 网络代理 }------------------------------------------
|
||||||
|
|
||||||
|
public static ProxySetup proxySetup() {
|
||||||
|
return ProxySetup.valueOf(StringUtils.upperCase(guiConfig.getProxySetup()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void proxyTestUrl(String url) {
|
||||||
|
guiConfig.setProxyTestUrl(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String proxyTestUrl() {
|
||||||
|
return guiConfig.getProxyTestUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void proxySetup(ProxySetup setup) {
|
||||||
|
guiConfig.setProxySetup(setup.getName());
|
||||||
|
|
||||||
|
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 (guiConfig == 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 = guiConfig.getProxyInfo();
|
||||||
|
|
||||||
|
if (proxyInfo == null)
|
||||||
|
setProxyInfo(new ProxyInfo());
|
||||||
|
|
||||||
|
return guiConfig.getProxyInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setProxyInfo(ProxyInfo info) {
|
||||||
|
guiConfig.setProxyInfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String proxyHost() {
|
||||||
|
return getProxyInfo().getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void proxyHost(String host) {
|
||||||
|
final Matcher matcher = PatternPool.IPV4.matcher(host);
|
||||||
|
if (!matcher.matches()) return;
|
||||||
|
|
||||||
|
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 (!NumberUtil.isNumber(port)) return;
|
||||||
|
|
||||||
|
getProxyInfo().setPort(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkProxy(BiConsumer<Boolean, String> consumer) {
|
||||||
|
if (ProxySetup.SYSTEM.equals(proxySetup())) {
|
||||||
|
consumer.accept(true, "");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!hasProxy()) return;
|
||||||
|
|
||||||
|
ThreadPoolManager.getInstance().execute(() -> {
|
||||||
|
try {
|
||||||
|
InetSocketAddress address = NetUtil.createAddress(proxyHost(), getProxyPort());
|
||||||
|
if (NetUtil.isOpen(address, 1000)) {
|
||||||
|
Platform.runLater(() -> consumer.accept(true, "success"));
|
||||||
|
} else {
|
||||||
|
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 Locale language() {
|
||||||
|
String language = guiConfig.getLanguage();
|
||||||
|
return LocaleUtils.toLocale(Optional.ofNullable(language).orElse(DEFAULT_LANGUAGE.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void language(Locale locale) {
|
||||||
|
guiConfig.setLanguage(locale.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 开机自启 }------------------------------------------
|
||||||
|
|
||||||
|
public static boolean autoStart() {
|
||||||
|
return guiConfig.getAutoStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void autoStart(Boolean autoStart) {
|
||||||
|
guiConfig.setAutoStart(autoStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 静默启动 }------------------------------------------
|
||||||
|
|
||||||
|
public static boolean silentStartup() {
|
||||||
|
return guiConfig.getSilentStartup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void silentStartup(Boolean startup) {
|
||||||
|
guiConfig.setSilentStartup(startup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 最小化到托盘 }------------------------------------------
|
||||||
|
|
||||||
|
public static boolean closeToTray() {
|
||||||
|
return guiConfig.getCloseToTray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeToTray(boolean check) {
|
||||||
|
guiConfig.setCloseToTray(check);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------{ 版本检查 }------------------------------------------
|
||||||
|
|
||||||
|
public static UpgradeConfig upgradeConfig() {
|
||||||
|
return guiConfig.getUpgradeConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AList aList() {
|
||||||
|
return upgradeConfig().getAList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String aListVersion() {
|
||||||
|
return aList().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringProperty aListVersionProperty() {
|
||||||
|
return aList().versionProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void aListVersion(String version) {
|
||||||
|
aListVersionProperty().set(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Gui gui() {
|
||||||
|
return upgradeConfig().getGui();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String guiVersion() {
|
||||||
|
return gui().getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void guiVersion(String version) {
|
||||||
|
gui().setVersion(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
256
src/main/java/cn/octopusyan/alistgui/manager/ConsoleLog.java
Normal file
256
src/main/java/cn/octopusyan/alistgui/manager/ConsoleLog.java
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
package cn.octopusyan.alistgui.manager;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.Popover;
|
||||||
|
import atlantafx.base.util.BBCodeParser;
|
||||||
|
import cn.hutool.core.swing.clipboard.ClipboardUtil;
|
||||||
|
import cn.hutool.core.util.ReUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.control.Hyperlink;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.scene.text.TextFlow;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟控制台输出
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class ConsoleLog {
|
||||||
|
public static final String format = "yyyy/MM/dd hh:mm:ss";
|
||||||
|
private volatile static ConsoleLog log;
|
||||||
|
private final VBox textArea;
|
||||||
|
private final static String CONSOLE_COLOR_PREFIX = "^\033[";
|
||||||
|
private final static String CONSOLE_MSG_REX = "^\033\\[(\\d+)m(.*)\033\\[0m(.*)$";
|
||||||
|
private final static String URL_IP_REX = "^((ht|f)tps?:\\/\\/)?[\\w-+&@#/%?=~_|!:,.;]*[\\w-+&@#/%=~_|]+(:\\d{1,5})?\\/?$";
|
||||||
|
|
||||||
|
private ConsoleLog(ScrollPane logAreaSp, VBox textArea) {
|
||||||
|
this.textArea = textArea;
|
||||||
|
|
||||||
|
textArea.heightProperty().subscribe(() -> logAreaSp.vvalueProperty().setValue(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConsoleLog getInstance() {
|
||||||
|
if (log == null) {
|
||||||
|
throw new RuntimeException("are you ready ?");
|
||||||
|
}
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init(ScrollPane logAreaSp, VBox textArea) {
|
||||||
|
synchronized (ConsoleLog.class) {
|
||||||
|
log = new ConsoleLog(logAreaSp, textArea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isInit() {
|
||||||
|
return log != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void info(String message, Object... param) {
|
||||||
|
info("", message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void warning(String message, Object... param) {
|
||||||
|
warning("", message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void error(String message, Object... param) {
|
||||||
|
error("", message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void info(String tag, String message, Object... param) {
|
||||||
|
printLog(tag, Level.INFO, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void warning(String tag, String message, Object... param) {
|
||||||
|
printLog(tag, Level.WARN, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void error(String tag, String message, Object... param) {
|
||||||
|
printLog(tag, Level.ERROR, message, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void msg(String message, Object... param) {
|
||||||
|
if (StringUtils.isEmpty(message) || !isInit()) return;
|
||||||
|
message = message.strip();
|
||||||
|
message = StrUtil.format(message, param);
|
||||||
|
|
||||||
|
// 多颜色消息处理
|
||||||
|
if (StringUtils.countMatches(message, CONSOLE_COLOR_PREFIX) > 1) {
|
||||||
|
String[] split = message.replace(CONSOLE_MSG_REX, "\n%s".formatted(CONSOLE_COLOR_PREFIX)).split("\n");
|
||||||
|
List<String> msgs = Arrays.stream(split).toList();
|
||||||
|
for (String msg : msgs) {
|
||||||
|
msg(msg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message = setPwdText(message);
|
||||||
|
message = resetConsoleColor(message);
|
||||||
|
|
||||||
|
print(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void printLog(String tag, Level level, String message, Object... param) {
|
||||||
|
if (!isInit()) return;
|
||||||
|
|
||||||
|
// 时间
|
||||||
|
String time = DateFormatUtils.format(new Date(), format);
|
||||||
|
time = STR."[color=-color-accent-emphasis]\{time}[/color]";
|
||||||
|
// 级别
|
||||||
|
String levelStr = resetLevelColor(level);
|
||||||
|
// 标签
|
||||||
|
tag = StringUtils.isEmpty(tag) ? "" : STR."\{tag}: ";
|
||||||
|
// 消息
|
||||||
|
message = STR."[color=-color-fg-muted]\{StrUtil.format(message, param)}[/color]";
|
||||||
|
|
||||||
|
// 拼接后输出
|
||||||
|
String input = STR."\{time} \{levelStr} - \{tag}\{message}";
|
||||||
|
|
||||||
|
print(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void print(String message) {
|
||||||
|
|
||||||
|
// 标记链接
|
||||||
|
String regex = STR.".*(\{AListManager.scheme()}|\{URL_IP_REX}).*";
|
||||||
|
if (ReUtil.isMatch(regex, message)) {
|
||||||
|
String text = ReUtil.get(regex, message, 1);
|
||||||
|
String url = text.startsWith("http") ? text : STR."http://\{text}";
|
||||||
|
url = url.replace("0.0.0.0", "127.0.0.1");
|
||||||
|
message = message.replace(text, STR."[url=\{url}]\{text}[/url]");
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFlow text = BBCodeParser.createFormattedText(STR."\{message}");
|
||||||
|
// 处理链接
|
||||||
|
setLink(text);
|
||||||
|
|
||||||
|
Platform.runLater(() -> log.textArea.getChildren().add(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
//==========================================={ 私有方法 }===================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将密码标记为link,一起处理点击事件
|
||||||
|
*
|
||||||
|
* @param msg 输出信息
|
||||||
|
* @return 处理后的信息
|
||||||
|
*/
|
||||||
|
private static String setPwdText(String msg) {
|
||||||
|
if (!ReUtil.isMatch(AListManager.PASSWORD_MSG_REG, msg)) return msg;
|
||||||
|
|
||||||
|
String password = ReUtil.get(AListManager.PASSWORD_MSG_REG, msg, 2);
|
||||||
|
AListManager.tmpPassword(password);
|
||||||
|
return msg.replace(password, STR."[url=\{password}]\{password}[/url]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理文本流中的链接
|
||||||
|
*
|
||||||
|
* @param text 文本流
|
||||||
|
*/
|
||||||
|
private static void setLink(TextFlow text) {
|
||||||
|
|
||||||
|
text.getChildren().forEach(child -> {
|
||||||
|
switch (child) {
|
||||||
|
case Hyperlink link -> link.setOnAction(_ -> {
|
||||||
|
String linkText = link.getUserData().toString();
|
||||||
|
if (ReUtil.isMatch(URL_IP_REX, linkText)) {
|
||||||
|
Context.getApplication().getHostServices().showDocument(linkText);
|
||||||
|
} else {
|
||||||
|
ClipboardUtil.setStr(linkText);
|
||||||
|
var pop = new Popover(new Text(Context.getLanguageBinding("msg.alist.pwd.copy").get()));
|
||||||
|
pop.show(link);
|
||||||
|
}
|
||||||
|
link.setVisited(false);
|
||||||
|
});
|
||||||
|
case TextFlow flow -> setLink(flow);
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 控制台输出颜色
|
||||||
|
*
|
||||||
|
* @param msg alist 输出消息
|
||||||
|
* @return bbcode 颜色文本
|
||||||
|
*/
|
||||||
|
private static String resetConsoleColor(String msg) {
|
||||||
|
if (!msg.contains("\033[")) return msg;
|
||||||
|
|
||||||
|
String colorCode = ReUtil.get(CONSOLE_MSG_REX, msg, 1);
|
||||||
|
String color = StringUtils.lowerCase(Color.valueOf(Integer.parseInt(colorCode)).getColor());
|
||||||
|
String colorMsg = ReUtil.get(CONSOLE_MSG_REX, msg, 2);
|
||||||
|
msg = ReUtil.get(CONSOLE_MSG_REX, msg, 3);
|
||||||
|
return color(color, colorMsg) + msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param level 级别
|
||||||
|
* @return bbcode 颜色
|
||||||
|
*/
|
||||||
|
private static String resetLevelColor(Level level) {
|
||||||
|
return color(level.getColor(), level.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String color(String color, String msg) {
|
||||||
|
String PREFIX = STR."\{StringUtils.isEmpty(color) ? "" : STR."[color=\{color}]"}";
|
||||||
|
String SUFFIX = STR."\{StringUtils.isEmpty(color) ? "" : "[/color]"}";
|
||||||
|
return STR."\{PREFIX}\{msg}\{SUFFIX}";
|
||||||
|
}
|
||||||
|
|
||||||
|
//============================{ 枚举 }================================
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Level {
|
||||||
|
INFO("INFO", null),
|
||||||
|
WARN("WARN", "-color-danger-emphasis"),
|
||||||
|
ERROR("ERROR", "-color-danger-fg"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
enum Color {
|
||||||
|
BLACK(30, "-color-fg-default"),
|
||||||
|
RED(31, "-color-danger-fg"),
|
||||||
|
GREEN(32, "-color-success-fg"),
|
||||||
|
YELLOW(33, "-color-warning-emphasis"),
|
||||||
|
BLUE(34, "-color-accent-fg"),
|
||||||
|
PINKISH_RED(35, "-color-danger-4"),
|
||||||
|
CYAN(36, "-color-accent-emphasis"),
|
||||||
|
WHITE(37, "-color-bg-default");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String color;
|
||||||
|
|
||||||
|
public static final Map<String, Color> NAME_CODE = Arrays.stream(Color.values())
|
||||||
|
.collect(Collectors.toMap(Color::name, Function.identity()));
|
||||||
|
|
||||||
|
public static final Map<Integer, Color> CODE_NAME = Arrays.stream(Color.values())
|
||||||
|
.collect(Collectors.toMap(Color::getCode, Function.identity()));
|
||||||
|
|
||||||
|
public static Color valueOf(int code) {
|
||||||
|
return CODE_NAME.get(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,192 @@
|
|||||||
|
package cn.octopusyan.alistgui.manager;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.Application;
|
||||||
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.util.WindowsUtil;
|
||||||
|
import cn.octopusyan.alistgui.view.PopupMenu;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统托盘管理
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SystemTrayManager {
|
||||||
|
// 托盘工具
|
||||||
|
private static final SystemTray systemTray;
|
||||||
|
private static TrayIcon trayIcon;
|
||||||
|
private static PopupMenu popupMenu;
|
||||||
|
|
||||||
|
static {
|
||||||
|
//检查系统是否支持托盘
|
||||||
|
if (!SystemTray.isSupported()) {
|
||||||
|
//系统托盘不支持
|
||||||
|
log.info("{}:系统托盘不支持", Thread.currentThread().getStackTrace()[1].getClassName());
|
||||||
|
systemTray = null;
|
||||||
|
} else {
|
||||||
|
systemTray = SystemTray.getSystemTray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void toolTip(String toptip) {
|
||||||
|
if (trayIcon == null) return;
|
||||||
|
|
||||||
|
trayIcon.setToolTip(toptip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void icon(String path) {
|
||||||
|
if (trayIcon == null) return;
|
||||||
|
icon(WindowsUtil.class.getResource(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void icon(URL url) {
|
||||||
|
if (trayIcon == null) return;
|
||||||
|
icon(Toolkit.getDefaultToolkit().getImage(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void icon(Image image) {
|
||||||
|
if (trayIcon == null) return;
|
||||||
|
trayIcon.setImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isShowing() {
|
||||||
|
if (systemTray == null) return false;
|
||||||
|
|
||||||
|
return List.of(systemTray.getTrayIcons()).contains(trayIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void show() {
|
||||||
|
|
||||||
|
// 是否启用托盘
|
||||||
|
if (!ConfigManager.closeToTray() || systemTray == null) {
|
||||||
|
if (trayIcon != null && isShowing()) {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initTrayIcon(AListManager.isRunning());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isShowing())
|
||||||
|
systemTray.add(trayIcon);
|
||||||
|
} catch (AWTException e) {
|
||||||
|
//系统托盘添加失败
|
||||||
|
log.error("{}:系统添加失败", Thread.currentThread().getStackTrace()[1].getClassName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hide() {
|
||||||
|
if (systemTray == null) return;
|
||||||
|
|
||||||
|
systemTray.remove(trayIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
//========================================={ private }===========================================
|
||||||
|
|
||||||
|
private static void initTrayIcon(boolean running) {
|
||||||
|
if (trayIcon != null) return;
|
||||||
|
|
||||||
|
// 系统托盘图标
|
||||||
|
URL resource = WindowsUtil.class.getResource(STR."/assets/logo\{running ? "" : "-disabled"}.png");
|
||||||
|
Image image = Toolkit.getDefaultToolkit().getImage(resource);
|
||||||
|
trayIcon = new TrayIcon(image);
|
||||||
|
|
||||||
|
// 设置图标尺寸自动适应
|
||||||
|
trayIcon.setImageAutoSize(true);
|
||||||
|
|
||||||
|
// 弹出式菜单组件
|
||||||
|
// trayIcon.setPopupMenu(getMenu());
|
||||||
|
|
||||||
|
// 鼠标移到系统托盘,会显示提示文本
|
||||||
|
toolTip(Constants.APP_TITLE);
|
||||||
|
|
||||||
|
// 鼠标监听
|
||||||
|
trayIcon.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent event) {
|
||||||
|
maybeShowPopup(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent event) {
|
||||||
|
maybeShowPopup(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeShowPopup(MouseEvent event) {
|
||||||
|
// popup menu trigger event
|
||||||
|
if (event.isPopupTrigger()) {
|
||||||
|
// 弹出菜单
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
initPopupMenu(running);
|
||||||
|
popupMenu.show(event);
|
||||||
|
});
|
||||||
|
} else if (event.getButton() == MouseEvent.BUTTON1) {
|
||||||
|
// 显示 PrimaryStage
|
||||||
|
Platform.runLater(() -> Application.getPrimaryStage().show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建托盘菜单
|
||||||
|
*/
|
||||||
|
private static void initPopupMenu(boolean running) {
|
||||||
|
if (popupMenu != null) return;
|
||||||
|
|
||||||
|
MenuItem start = PopupMenu.menuItem(
|
||||||
|
getStringBinding(STR."main.control.\{running ? "stop" : "start"}"),
|
||||||
|
_ -> AListManager.openScheme()
|
||||||
|
);
|
||||||
|
MenuItem browser = PopupMenu.menuItem(getStringBinding("main.more.browser"), _ -> AListManager.openScheme());
|
||||||
|
browser.setDisable(!running);
|
||||||
|
|
||||||
|
AListManager.runningProperty().addListener((_, _, newValue) -> {
|
||||||
|
start.textProperty().unbind();
|
||||||
|
start.textProperty().bind(getStringBinding(STR."main.control.\{newValue ? "stop" : "start"}"));
|
||||||
|
browser.disableProperty().set(!newValue);
|
||||||
|
toolTip(STR."AList \{newValue ? "running" : "stopped"}");
|
||||||
|
icon(STR."/assets/logo\{newValue ? "" : "-disabled"}.png");
|
||||||
|
});
|
||||||
|
|
||||||
|
popupMenu = new PopupMenu()
|
||||||
|
.addItem(new MenuItem(Constants.APP_TITLE), _ -> stage().show())
|
||||||
|
.addSeparator()
|
||||||
|
.addCaptionItem("AList")
|
||||||
|
.addItem(start, _ -> {
|
||||||
|
if (AListManager.isRunning()) {
|
||||||
|
AListManager.stop();
|
||||||
|
} else {
|
||||||
|
AListManager.start();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addItem(getStringBinding("main.control.restart"), _ -> AListManager.restart())
|
||||||
|
.addMenu(getStringBinding("main.control.more"), browser,
|
||||||
|
PopupMenu.menuItem(getStringBinding("main.more.open-config"), _ -> AListManager.openConfig()),
|
||||||
|
PopupMenu.menuItem(getStringBinding("main.more.open-log"), _ -> AListManager.openLogFolder()))
|
||||||
|
.addSeparator()
|
||||||
|
.addExitItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBinding getStringBinding(String key) {
|
||||||
|
return Context.getLanguageBinding(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stage stage() {
|
||||||
|
return WindowsUtil.getStage();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package cn.octopusyan.alistgui.manager.http;
|
package cn.octopusyan.alistgui.manager.http;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -14,7 +15,6 @@ import java.net.http.HttpClient;
|
|||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@ -23,8 +23,15 @@ import java.util.concurrent.Executor;
|
|||||||
*
|
*
|
||||||
* @author octopus_yan@foxmail.com
|
* @author octopus_yan@foxmail.com
|
||||||
*/
|
*/
|
||||||
|
@Data
|
||||||
public class HttpConfig {
|
public class HttpConfig {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class);
|
private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class);
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 使用系统默认代理
|
||||||
|
System.setProperty("java.net.useSystemProxies", "true");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* http版本
|
* http版本
|
||||||
*/
|
*/
|
||||||
@ -84,19 +91,18 @@ public class HttpConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
sslParameters = new SSLParameters();
|
sslParameters = new SSLParameters();
|
||||||
sslParameters.setEndpointIdentificationAlgorithm("");
|
sslParameters.setEndpointIdentificationAlgorithm("");
|
||||||
|
sslParameters.setProtocols(new String[]{"TLSv1.2"});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sslContext = SSLContext.getInstance("TLSv1.2");
|
sslContext = SSLContext.getInstance("TLSv1.2");
|
||||||
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证
|
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证
|
||||||
@ -104,80 +110,5 @@ public class HttpConfig {
|
|||||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
logger.error("", 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,7 +1,14 @@
|
|||||||
package cn.octopusyan.alistgui.manager.http;
|
package cn.octopusyan.alistgui.manager.http;
|
||||||
|
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import cn.octopusyan.alistgui.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.alistgui.manager.http.handler.BodyHandler;
|
||||||
|
import cn.octopusyan.alistgui.model.ProxyInfo;
|
||||||
|
import cn.octopusyan.alistgui.util.JsonUtil;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import lombok.Getter;
|
||||||
|
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,17 +18,22 @@ 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;
|
||||||
|
@Getter
|
||||||
private volatile HttpClient httpClient;
|
private volatile HttpClient httpClient;
|
||||||
private final HttpConfig httpConfig;
|
private final HttpConfig httpConfig;
|
||||||
|
|
||||||
@ -57,15 +69,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 +93,20 @@ public class HttpUtil {
|
|||||||
httpClient = createClient(httpConfig);
|
httpClient = createClient(httpConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpClient getHttpClient() {
|
public String get(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
|
||||||
return httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String get(String uri, JSONObject header, JSONObject 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()));
|
.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 +114,65 @@ 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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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), 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.alistgui.manager.http.handler;
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
src/main/java/cn/octopusyan/alistgui/model/AListConfig.java
Normal file
207
src/main/java/cn/octopusyan/alistgui/model/AListConfig.java
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package cn.octopusyan.alistgui.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alist 配置文件 model
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class AListConfig {
|
||||||
|
|
||||||
|
@JsonProperty("force")
|
||||||
|
private Boolean force;
|
||||||
|
@JsonProperty("site_url")
|
||||||
|
private String siteUrl;
|
||||||
|
@JsonProperty("cdn")
|
||||||
|
private String cdn;
|
||||||
|
@JsonProperty("jwt_secret")
|
||||||
|
private String jwtSecret;
|
||||||
|
@JsonProperty("token_expires_in")
|
||||||
|
private Integer tokenExpiresIn;
|
||||||
|
@JsonProperty("database")
|
||||||
|
private Database database;
|
||||||
|
@JsonProperty("meilisearch")
|
||||||
|
private MeiliSearch meilisearch;
|
||||||
|
@JsonProperty("scheme")
|
||||||
|
private Scheme scheme;
|
||||||
|
@JsonProperty("temp_dir")
|
||||||
|
private String tempDir;
|
||||||
|
@JsonProperty("bleve_dir")
|
||||||
|
private String bleveDir;
|
||||||
|
@JsonProperty("dist_dir")
|
||||||
|
private String distDir;
|
||||||
|
@JsonProperty("log")
|
||||||
|
private Log log;
|
||||||
|
@JsonProperty("delayed_start")
|
||||||
|
private Integer delayedStart;
|
||||||
|
@JsonProperty("max_connections")
|
||||||
|
private Integer maxConnections;
|
||||||
|
@JsonProperty("tls_insecure_skip_verify")
|
||||||
|
private Boolean tlsInsecureSkipVerify;
|
||||||
|
@JsonProperty("tasks")
|
||||||
|
private Tasks tasks;
|
||||||
|
@JsonProperty("cors")
|
||||||
|
private Cors cors;
|
||||||
|
@JsonProperty("s3")
|
||||||
|
private S3 s3;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Database {
|
||||||
|
@JsonProperty("type")
|
||||||
|
private String type;
|
||||||
|
@JsonProperty("host")
|
||||||
|
private String host;
|
||||||
|
@JsonProperty("port")
|
||||||
|
private Integer port;
|
||||||
|
@JsonProperty("user")
|
||||||
|
private String user;
|
||||||
|
@JsonProperty("password")
|
||||||
|
private String password;
|
||||||
|
@JsonProperty("name")
|
||||||
|
private String name;
|
||||||
|
@JsonProperty("db_file")
|
||||||
|
private String dbFile;
|
||||||
|
@JsonProperty("table_prefix")
|
||||||
|
private String tablePrefix;
|
||||||
|
@JsonProperty("ssl_mode")
|
||||||
|
private String sslMode;
|
||||||
|
@JsonProperty("dsn")
|
||||||
|
private String dsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class MeiliSearch {
|
||||||
|
@JsonProperty("host")
|
||||||
|
private String host;
|
||||||
|
@JsonProperty("api_key")
|
||||||
|
private String apiKey;
|
||||||
|
@JsonProperty("index_prefix")
|
||||||
|
private String indexPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Scheme {
|
||||||
|
@JsonProperty("address")
|
||||||
|
private String address;
|
||||||
|
@JsonProperty("http_port")
|
||||||
|
private Integer httpPort;
|
||||||
|
@JsonProperty("https_port")
|
||||||
|
private Integer httpsPort;
|
||||||
|
@JsonProperty("force_https")
|
||||||
|
private Boolean forceHttps;
|
||||||
|
@JsonProperty("cert_file")
|
||||||
|
private String certFile;
|
||||||
|
@JsonProperty("key_file")
|
||||||
|
private String keyFile;
|
||||||
|
@JsonProperty("unix_file")
|
||||||
|
private String unixFile;
|
||||||
|
@JsonProperty("unix_file_perm")
|
||||||
|
private String unixFilePerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Log {
|
||||||
|
@JsonProperty("enable")
|
||||||
|
private Boolean enable;
|
||||||
|
@JsonProperty("name")
|
||||||
|
private String name;
|
||||||
|
@JsonProperty("max_size")
|
||||||
|
private Integer maxSize;
|
||||||
|
@JsonProperty("max_backups")
|
||||||
|
private Integer maxBackups;
|
||||||
|
@JsonProperty("max_age")
|
||||||
|
private Integer maxAge;
|
||||||
|
@JsonProperty("compress")
|
||||||
|
private Boolean compress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Tasks {
|
||||||
|
@JsonProperty("download")
|
||||||
|
private Download download;
|
||||||
|
@JsonProperty("transfer")
|
||||||
|
private Transfer transfer;
|
||||||
|
@JsonProperty("upload")
|
||||||
|
private Upload upload;
|
||||||
|
@JsonProperty("copy")
|
||||||
|
private Copy copy;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Download {
|
||||||
|
@JsonProperty("workers")
|
||||||
|
private Integer workers;
|
||||||
|
@JsonProperty("max_retry")
|
||||||
|
private Integer maxRetry;
|
||||||
|
@JsonProperty("task_persistant")
|
||||||
|
private Boolean taskPersistant;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Transfer {
|
||||||
|
@JsonProperty("workers")
|
||||||
|
private Integer workers;
|
||||||
|
@JsonProperty("max_retry")
|
||||||
|
private Integer maxRetry;
|
||||||
|
@JsonProperty("task_persistant")
|
||||||
|
private Boolean taskPersistant;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Upload {
|
||||||
|
@JsonProperty("workers")
|
||||||
|
private Integer workers;
|
||||||
|
@JsonProperty("max_retry")
|
||||||
|
private Integer maxRetry;
|
||||||
|
@JsonProperty("task_persistant")
|
||||||
|
private Boolean taskPersistant;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Copy {
|
||||||
|
@JsonProperty("workers")
|
||||||
|
private Integer workers;
|
||||||
|
@JsonProperty("max_retry")
|
||||||
|
private Integer maxRetry;
|
||||||
|
@JsonProperty("task_persistant")
|
||||||
|
private Boolean taskPersistant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Cors {
|
||||||
|
@JsonProperty("allow_origins")
|
||||||
|
private List<String> allowOrigins;
|
||||||
|
@JsonProperty("allow_methods")
|
||||||
|
private List<String> allowMethods;
|
||||||
|
@JsonProperty("allow_headers")
|
||||||
|
private List<String> allowHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class S3 {
|
||||||
|
@JsonProperty("enable")
|
||||||
|
private Boolean enable;
|
||||||
|
@JsonProperty("port")
|
||||||
|
private Integer port;
|
||||||
|
@JsonProperty("ssl")
|
||||||
|
private Boolean ssl;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java
Normal file
31
src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package cn.octopusyan.alistgui.model;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GUI配置信息
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class GuiConfig {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GuiConfig.class);
|
||||||
|
|
||||||
|
private Boolean autoStart = false;
|
||||||
|
private Boolean silentStartup = false;
|
||||||
|
private Boolean closeToTray = true;
|
||||||
|
@JsonProperty("proxy")
|
||||||
|
private ProxyInfo proxyInfo;
|
||||||
|
@JsonProperty("proxy.testUrl")
|
||||||
|
private String proxyTestUrl = "http://";
|
||||||
|
private String proxySetup = ProxySetup.NO_PROXY.getName();
|
||||||
|
private String language = ConfigManager.DEFAULT_LANGUAGE.toString();
|
||||||
|
private String theme = ConfigManager.DEFAULT_THEME;
|
||||||
|
@JsonProperty("upgrade")
|
||||||
|
private UpgradeConfig upgradeConfig = new UpgradeConfig();
|
||||||
|
}
|
||||||
16
src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java
Normal file
16
src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package cn.octopusyan.alistgui.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理信息
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ProxyInfo {
|
||||||
|
private String host = "";
|
||||||
|
private String port = "";
|
||||||
|
private String username = "";
|
||||||
|
private String password = "";
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package cn.octopusyan.alistgui.model;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.AList;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.Gui;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UpgradeConfig {
|
||||||
|
private AList aList = new AList();
|
||||||
|
private Gui gui = new Gui();
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package cn.octopusyan.alistgui.model.upgrade;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AList implements UpgradeApp {
|
||||||
|
@JsonIgnore
|
||||||
|
private final String owner = "alist-org";
|
||||||
|
@JsonIgnore
|
||||||
|
private final String repo = "alist";
|
||||||
|
|
||||||
|
private String releaseFile = "alist-windows-amd64.zip";
|
||||||
|
private StringProperty version = new SimpleStringProperty("unknown");
|
||||||
|
|
||||||
|
public StringProperty versionProperty() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(String version) {
|
||||||
|
this.version.set(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/cn/octopusyan/alistgui/model/upgrade/Gui.java
Normal file
27
src/main/java/cn/octopusyan/alistgui/model/upgrade/Gui.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package cn.octopusyan.alistgui.model.upgrade;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.util.PropertiesUtils;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class Gui implements UpgradeApp {
|
||||||
|
@JsonIgnore
|
||||||
|
private final String owner = "alist-org";
|
||||||
|
@JsonIgnore
|
||||||
|
private final String repo = "alist";
|
||||||
|
|
||||||
|
private String releaseFile = "alist-gui-{version}-windows.zip";
|
||||||
|
private String version = PropertiesUtils.getInstance().getProperty("app.version");
|
||||||
|
|
||||||
|
public String getReleaseFile() {
|
||||||
|
return getReleaseFile(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReleaseFile(String version) {
|
||||||
|
return releaseFile.replace("{version}", version);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package cn.octopusyan.alistgui.model.upgrade;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public interface UpgradeApp {
|
||||||
|
@JsonIgnore
|
||||||
|
default String getReleaseApi() {
|
||||||
|
return STR."https://api.github.com/repos/\{getOwner()}/\{getRepo()}/releases/latest";
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
default String getDownloadUrl(String version) {
|
||||||
|
return STR."https://github.com/\{getOwner()}/\{getRepo()}/releases/download/\{version}/\{getReleaseFile()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
String getOwner();
|
||||||
|
|
||||||
|
String getRepo();
|
||||||
|
|
||||||
|
String getReleaseFile();
|
||||||
|
|
||||||
|
String getVersion();
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package cn.octopusyan.alistgui.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseTask;
|
||||||
|
import cn.octopusyan.alistgui.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
|
||||||
|
import cn.octopusyan.alistgui.util.JsonUtil;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查更新任务
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class CheckUpdateTask extends BaseTask {
|
||||||
|
private final UpgradeApp app;
|
||||||
|
|
||||||
|
public CheckUpdateTask(UpgradeApp app) {
|
||||||
|
super(STR."check update \{app.getRepo()}");
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void task() throws Exception {
|
||||||
|
String responseStr = HttpUtil.getInstance().get(app.getReleaseApi(), null, null);
|
||||||
|
JsonNode response = JsonUtil.parseJsonObject(responseStr);
|
||||||
|
|
||||||
|
// TODO 校验返回内容
|
||||||
|
String newVersion = response.get("tag_name").asText();
|
||||||
|
|
||||||
|
if (listener != null && listener instanceof UpgradeListener lis)
|
||||||
|
lis.onChecked(!StringUtils.equals(app.getVersion(), newVersion), newVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UpgradeListener extends BaseTask.Listener {
|
||||||
|
@Override
|
||||||
|
default void onSucceeded() {
|
||||||
|
// do nothing ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChecked(boolean hasUpgrade, String version);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/main/java/cn/octopusyan/alistgui/task/DownloadTask.java
Normal file
38
src/main/java/cn/octopusyan/alistgui/task/DownloadTask.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package cn.octopusyan.alistgui.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseTask;
|
||||||
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
|
import cn.octopusyan.alistgui.manager.http.HttpUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO 下载任务
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DownloadTask extends BaseTask {
|
||||||
|
private final String downloadUrl;
|
||||||
|
|
||||||
|
public DownloadTask(String downloadUrl) {
|
||||||
|
super(STR."Download \{downloadUrl}");
|
||||||
|
this.downloadUrl = downloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onListen(DownloadListener listener) {
|
||||||
|
super.onListen(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void task() throws Exception {
|
||||||
|
HttpUtil.getInstance().download(
|
||||||
|
downloadUrl,
|
||||||
|
Constants.BIN_DIR_PATH,
|
||||||
|
listener instanceof DownloadListener ? ((DownloadListener) listener)::onProgress : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DownloadListener extends BaseTask.Listener {
|
||||||
|
void onProgress(Long total, Long progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
package cn.octopusyan.alistgui.task;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseTask;
|
||||||
|
import cn.octopusyan.alistgui.manager.http.HttpUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理检测任务
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ProxyCheckTask extends BaseTask {
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
package cn.octopusyan.alistgui.task.listener;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseTask;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConsoleLog;
|
||||||
|
import cn.octopusyan.alistgui.task.CheckUpdateTask;
|
||||||
|
import cn.octopusyan.alistgui.task.DownloadTask;
|
||||||
|
import cn.octopusyan.alistgui.view.alert.AlertUtil;
|
||||||
|
import cn.octopusyan.alistgui.view.alert.builder.ProgressBuilder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务监听器默认实现
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class TaskListener implements BaseTask.Listener {
|
||||||
|
private final BaseTask task;
|
||||||
|
// 加载弹窗
|
||||||
|
final ProgressBuilder progress = AlertUtil.progress();
|
||||||
|
|
||||||
|
public TaskListener(BaseTask task) {
|
||||||
|
this.task = task;
|
||||||
|
progress.onCancel(task::cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
log.info(STR."\{task.getName()} start ...");
|
||||||
|
ConsoleLog.info(task.getName(), "start ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRunning() {
|
||||||
|
progress.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled() {
|
||||||
|
progress.close();
|
||||||
|
log.info(STR."\{task.getName()} cancel ...");
|
||||||
|
ConsoleLog.info(task.getName(), "cancel ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailed(Throwable throwable) {
|
||||||
|
progress.close();
|
||||||
|
log.error(STR."\{task.getName()} fail ...", throwable);
|
||||||
|
ConsoleLog.error(task.getName(), STR."fail : \{throwable.getMessage()}");
|
||||||
|
onFail(throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSucceeded() {
|
||||||
|
progress.close();
|
||||||
|
log.info(STR."\{task.getName()} success ...");
|
||||||
|
ConsoleLog.info(task.getName(), "success ...");
|
||||||
|
onSucceed();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void onSucceed();
|
||||||
|
|
||||||
|
protected void onFail(Throwable throwable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载任务监听默认实现
|
||||||
|
*/
|
||||||
|
public static abstract class DownloadListener extends TaskListener implements DownloadTask.DownloadListener {
|
||||||
|
|
||||||
|
private volatile int lastProgress = 0;
|
||||||
|
|
||||||
|
public DownloadListener(BaseTask task) {
|
||||||
|
super(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgress(Long total, Long progress) {
|
||||||
|
int a = (int) (((double) progress / total) * 100);
|
||||||
|
if (a % 10 == 0) {
|
||||||
|
if (a != lastProgress) {
|
||||||
|
lastProgress = a;
|
||||||
|
ConsoleLog.info(STR."\{lastProgress} %");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查更新监听默认实现
|
||||||
|
*/
|
||||||
|
public static abstract class UpgradeUpgradeListener extends TaskListener implements CheckUpdateTask.UpgradeListener {
|
||||||
|
public UpgradeUpgradeListener(BaseTask task) {
|
||||||
|
super(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSucceed() {
|
||||||
|
// do nothing ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,228 +0,0 @@
|
|||||||
package cn.octopusyan.alistgui.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,28 +0,0 @@
|
|||||||
package cn.octopusyan.alistgui.util;
|
|
||||||
|
|
||||||
import javafx.scene.input.Clipboard;
|
|
||||||
import javafx.scene.input.ClipboardContent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 剪切板工具
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class ClipUtil {
|
|
||||||
//获取系统剪切板
|
|
||||||
private static final Clipboard clipboard = Clipboard.getSystemClipboard();
|
|
||||||
|
|
||||||
public static void setClip(String data) {
|
|
||||||
clipboard.clear();
|
|
||||||
// 设置剪切板内容
|
|
||||||
ClipboardContent clipboardContent = new ClipboardContent();
|
|
||||||
clipboardContent.putString(data);
|
|
||||||
clipboard.setContent(clipboardContent);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getString() {
|
|
||||||
// javafx 从剪切板获取文本
|
|
||||||
return clipboard.getString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
84
src/main/java/cn/octopusyan/alistgui/util/DownloadUtil.java
Normal file
84
src/main/java/cn/octopusyan/alistgui/util/DownloadUtil.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package cn.octopusyan.alistgui.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.core.util.ZipUtil;
|
||||||
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConsoleLog;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.AList;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
|
||||||
|
import cn.octopusyan.alistgui.task.DownloadTask;
|
||||||
|
import cn.octopusyan.alistgui.task.listener.TaskListener;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载工具
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class DownloadUtil {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文件
|
||||||
|
*
|
||||||
|
* @param app 应用
|
||||||
|
* @param version 下载版本
|
||||||
|
*/
|
||||||
|
public static DownloadTask startDownload(UpgradeApp app, String version, Runnable runnable) {
|
||||||
|
var task = new DownloadTask(app.getDownloadUrl(version));
|
||||||
|
task.onListen(new TaskListener.DownloadListener(task) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRunning() {
|
||||||
|
// 不展示进度条
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSucceed() {
|
||||||
|
String msg = STR."download \{app.getRepo()} success";
|
||||||
|
log.info(msg);
|
||||||
|
ConsoleLog.info(msg);
|
||||||
|
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unzip(UpgradeApp app) {
|
||||||
|
String parentPath = app instanceof AList ? Constants.BIN_DIR_PATH : Constants.DATA_DIR_PATH;
|
||||||
|
|
||||||
|
File file = new File(parentPath + File.separator + app.getReleaseFile());
|
||||||
|
ZipFile zipFile = ZipUtil.toZipFile(file, CharsetUtil.defaultCharset());
|
||||||
|
ZipUtil.read(zipFile, zipEntry -> {
|
||||||
|
String path = zipEntry.getName();
|
||||||
|
if (FileUtil.isWindows()) {
|
||||||
|
// Win系统下
|
||||||
|
path = StrUtil.replace(path, "*", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
final File outItemFile = FileUtil.file(parentPath, path);
|
||||||
|
if (zipEntry.isDirectory()) {
|
||||||
|
// 目录
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
outItemFile.mkdirs();
|
||||||
|
} else {
|
||||||
|
InputStream in = ZipUtil.getStream(zipFile, zipEntry);
|
||||||
|
// 文件
|
||||||
|
FileUtil.writeFromStream(in, outItemFile, false);
|
||||||
|
|
||||||
|
log.info(STR."unzip ==> \{outItemFile.getAbsoluteFile()}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解压完成后删除
|
||||||
|
FileUtil.del(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,135 +0,0 @@
|
|||||||
package cn.octopusyan.alistgui.util;
|
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.commons.io.filefilter.CanReadFileFilter;
|
|
||||||
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.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文件工具类
|
|
||||||
*
|
|
||||||
* @author octopus_yan@foxmail.com
|
|
||||||
*/
|
|
||||||
public class FileUtil {
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
|
|
||||||
|
|
||||||
public static File[] ls(String path) {
|
|
||||||
File dir = new File(path);
|
|
||||||
if (!dir.exists())
|
|
||||||
throw new RuntimeException(path + "不存在!");
|
|
||||||
|
|
||||||
if (!dir.isDirectory())
|
|
||||||
throw new RuntimeException(path + "不是一个文件夹!");
|
|
||||||
|
|
||||||
return dir.listFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void copyFilesFromDir(String path, String dest) throws IOException {
|
|
||||||
if (StringUtils.isBlank(path) || StringUtils.isBlank(dest)) {
|
|
||||||
logger.error("path is blank !");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File dir = new File(path);
|
|
||||||
if (!dir.exists()) {
|
|
||||||
logger.error("[" + path + "] 不存在!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!dir.isDirectory()) {
|
|
||||||
logger.error("[" + path + "] 不是一个文件夹!");
|
|
||||||
}
|
|
||||||
|
|
||||||
File[] files = dir.listFiles();
|
|
||||||
if (files == null) return;
|
|
||||||
|
|
||||||
File directory = new File(dest);
|
|
||||||
if (directory.exists() && !directory.isDirectory()) {
|
|
||||||
logger.error("[" + dest + "] 不是一个文件夹!");
|
|
||||||
}
|
|
||||||
|
|
||||||
FileUtils.forceMkdir(directory);
|
|
||||||
|
|
||||||
for (File file : files) {
|
|
||||||
copyFile(file, new File(dest + File.separator + file.getName()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void copyFile(File in, File out) throws IOException {
|
|
||||||
copyFile(Files.newInputStream(in.toPath()), out);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void copyFile(InputStream input, File out) throws IOException {
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
output = Files.newOutputStream(out.toPath());
|
|
||||||
byte[] buf = new byte[1024];
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = input.read(buf)) > 0) {
|
|
||||||
output.write(buf, 0, bytesRead);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("", e);
|
|
||||||
} finally {
|
|
||||||
if (output != null) input.close();
|
|
||||||
if (output != null) output.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文件主名称
|
|
||||||
*
|
|
||||||
* @param file 文件对象
|
|
||||||
* @return 文件名称
|
|
||||||
*/
|
|
||||||
public static String mainName(File file) {
|
|
||||||
//忽略判断
|
|
||||||
String fileName = file.getName();
|
|
||||||
return fileName.substring(0, fileName.lastIndexOf("."));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> listFileNames(String path) {
|
|
||||||
Collection<File> files = FileUtils.listFiles(new File(path), CanReadFileFilter.CAN_READ, null);
|
|
||||||
return files.stream().map(File::getName).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回被查找到的文件的绝对路径(匹配到一个就返回)
|
|
||||||
*
|
|
||||||
* @param root 根目录文件
|
|
||||||
* @param fileName 要找的文件名
|
|
||||||
* @return 绝对路径
|
|
||||||
*/
|
|
||||||
private static String findFiles(File root, String fileName) {
|
|
||||||
//定义一个返回值
|
|
||||||
String path = null;
|
|
||||||
//如果传进来的是目录,并且存在
|
|
||||||
if (root.exists() && root.isDirectory()) {
|
|
||||||
//遍历文件夹中的各个文件
|
|
||||||
File[] files = root.listFiles();
|
|
||||||
if (files != null) {
|
|
||||||
for (File file : files) {
|
|
||||||
//如果path的值没有变化
|
|
||||||
if (path == null) {
|
|
||||||
if (file.isFile() && file.getName().contains(fileName)) {
|
|
||||||
path = file.getAbsolutePath();
|
|
||||||
} else {
|
|
||||||
path = findFiles(file, fileName);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;//跳出循环,增加性能
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,9 +1,11 @@
|
|||||||
package cn.octopusyan.alistgui.util;
|
package cn.octopusyan.alistgui.util;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.JavaFXBuilderFactory;
|
import javafx.fxml.JavaFXBuilderFactory;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FXML 工具
|
* FXML 工具
|
||||||
@ -13,13 +15,17 @@ import java.nio.charset.StandardCharsets;
|
|||||||
public class FxmlUtil {
|
public class FxmlUtil {
|
||||||
|
|
||||||
public static FXMLLoader load(String name) {
|
public static FXMLLoader load(String name) {
|
||||||
|
return load(name, Context.getLanguageResource().get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FXMLLoader load(String name, ResourceBundle bundle) {
|
||||||
String prefix = "/fxml/";
|
String prefix = "/fxml/";
|
||||||
String suffix = ".fxml";
|
String suffix = ".fxml";
|
||||||
return new FXMLLoader(
|
return new FXMLLoader(
|
||||||
FxmlUtil.class.getResource(prefix + name + suffix),
|
FxmlUtil.class.getResource(prefix + name + suffix),
|
||||||
null,
|
bundle,
|
||||||
new JavaFXBuilderFactory(),
|
new JavaFXBuilderFactory(),
|
||||||
null,
|
Context.getControlFactory(),
|
||||||
StandardCharsets.UTF_8
|
StandardCharsets.UTF_8
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
187
src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java
Normal file
187
src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package cn.octopusyan.alistgui.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,67 +1,73 @@
|
|||||||
package cn.octopusyan.alistgui.util;
|
package cn.octopusyan.alistgui.util;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.exec.*;
|
import org.apache.commons.exec.*;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.File;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 命令工具类
|
* 命令工具类
|
||||||
*
|
*
|
||||||
* @author octopus_yan@foxmail.com
|
* @author octopus_yan@foxmail.com
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class ProcessesUtil {
|
public class ProcessesUtil {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ProcessesUtil.class);
|
private static final String NEW_LINE = System.lineSeparator();
|
||||||
private static final String NEWLINE = System.lineSeparator();
|
private static final int EXIT_VALUE = 1;
|
||||||
private static final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
|
|
||||||
|
|
||||||
public static boolean exec(String command) {
|
private final Executor executor = DefaultExecutor.builder().get();
|
||||||
try {
|
private final ShutdownHookProcessDestroyer processDestroyer;
|
||||||
exec(command, new OnExecuteListener() {
|
private OnExecuteListener listener;
|
||||||
@Override
|
private CommandLine commandLine;
|
||||||
public void onExecute(String msg) {
|
|
||||||
|
|
||||||
}
|
private static final Set<ProcessesUtil> set = new HashSet<>();
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void onExecuteSuccess(int exitValue) {
|
* Prevent construction.
|
||||||
|
*/
|
||||||
}
|
private ProcessesUtil(String workingDirectory) {
|
||||||
|
|
||||||
@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() {
|
LogOutputStream logout = new LogOutputStream() {
|
||||||
@Override
|
@Override
|
||||||
protected void processLine(String line, int logLevel) {
|
protected void processLine(String line, int logLevel) {
|
||||||
if (listener != null) listener.onExecute(line + NEWLINE);
|
if (listener != null)
|
||||||
|
listener.onExecute(line + NEW_LINE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
PumpStreamHandler streamHandler = new PumpStreamHandler(logout, logout);
|
||||||
|
executor.setStreamHandler(streamHandler);
|
||||||
|
executor.setWorkingDirectory(new File(workingDirectory));
|
||||||
|
executor.setExitValue(EXIT_VALUE);
|
||||||
|
processDestroyer = new ShutdownHookProcessDestroyer();
|
||||||
|
executor.setProcessDestroyer(processDestroyer);
|
||||||
|
}
|
||||||
|
|
||||||
CommandLine commandLine = CommandLine.parse(command);
|
public static ProcessesUtil init(String workingDirectory) {
|
||||||
DefaultExecutor executor = DefaultExecutor.builder().get();
|
ProcessesUtil util = new ProcessesUtil(workingDirectory);
|
||||||
executor.setStreamHandler(new PumpStreamHandler(logout, logout));
|
set.add(util);
|
||||||
|
return util;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exec(String command) {
|
||||||
|
commandLine = CommandLine.parse(command);
|
||||||
|
int execute = 0;
|
||||||
|
try {
|
||||||
|
execute = executor.execute(commandLine);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("exec", e);
|
||||||
|
}
|
||||||
|
return execute == EXIT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exec(String command, OnExecuteListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
commandLine = CommandLine.parse(command);
|
||||||
DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() {
|
DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void onProcessComplete(int exitValue) {
|
public void onProcessComplete(int exitValue) {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
listener.onExecuteSuccess(exitValue);
|
listener.onExecuteSuccess(exitValue == EXIT_VALUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,23 +80,31 @@ public class ProcessesUtil {
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
executor.execute(commandLine, handler);
|
executor.execute(commandLine, handler);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
if (listener != null) listener.onExecuteError(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 {
|
public interface OnExecuteListener {
|
||||||
void onExecute(String msg);
|
void onExecute(String msg);
|
||||||
|
|
||||||
void onExecuteSuccess(int exitValue);
|
default void onExecuteSuccess(boolean success) {
|
||||||
|
|
||||||
void onExecuteError(Exception e);
|
|
||||||
void onExecuteOver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
default void onExecuteError(Exception e) {
|
||||||
* Prevent construction.
|
}
|
||||||
*/
|
|
||||||
private ProcessesUtil() {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
src/main/java/cn/octopusyan/alistgui/util/Registry.java
Normal file
78
src/main/java/cn/octopusyan/alistgui/util/Registry.java
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package cn.octopusyan.alistgui.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册表编辑
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class Registry {
|
||||||
|
private static final ProcessesUtil util = ProcessesUtil.init(".");
|
||||||
|
|
||||||
|
public static void setStringValue(Root root, String keyPath, String name, String value) {
|
||||||
|
setValue(root, keyPath, name, DataType.REG_SZ, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void deleteValue(Root root, String keyPath, String name) {
|
||||||
|
name = handleSpaces(name);
|
||||||
|
util.exec(STR."reg \{Operation.DELETE} \{root.path}\\\{keyPath} /v \{name} /f");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setValue(Root root, String keyPath, String name, DataType type, String value) {
|
||||||
|
name = handleSpaces(name);
|
||||||
|
value = handleSpaces(value);
|
||||||
|
util.exec(STR."""
|
||||||
|
reg \{Operation.ADD} \{root.path}\\\{keyPath} /v \{name} /t \{type} /d \{value}
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String handleSpaces(String str) {
|
||||||
|
if (str.contains(" "))
|
||||||
|
str = STR."\"\{str}\"";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Operation {
|
||||||
|
ADD,
|
||||||
|
COMPARE,
|
||||||
|
COPY,
|
||||||
|
DELETE,
|
||||||
|
EXPORT,
|
||||||
|
IMPORT,
|
||||||
|
LOAD,
|
||||||
|
QUERY,
|
||||||
|
RESTORE,
|
||||||
|
SAVE,
|
||||||
|
UNLOAD,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Root {
|
||||||
|
HKCR("HKEY_CLASSES_ROOT"),
|
||||||
|
HKCU("HKEY_CURRENT_USER"),
|
||||||
|
HKLM("HKEY_LOCAL_MACHINE"),
|
||||||
|
HKU("HKEY_USERS"),
|
||||||
|
HKCC("HKEY_CURRENT_CONFIG"),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
Root(String path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DataType {
|
||||||
|
REG_SZ,
|
||||||
|
REG_MULTI_SZ,
|
||||||
|
REG_DWORD_BIG_ENDIAN,
|
||||||
|
REG_DWORD,
|
||||||
|
REG_BINARY,
|
||||||
|
REG_DWORD_LITTLE_ENDIAN,
|
||||||
|
REG_LINK,
|
||||||
|
REG_FULL_RESOURCE_DESCRIPTOR,
|
||||||
|
REG_EXPAND_SZ,
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/main/java/cn/octopusyan/alistgui/util/WindowsUtil.java
Normal file
69
src/main/java/cn/octopusyan/alistgui/util/WindowsUtil.java
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package cn.octopusyan.alistgui.util;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.Application;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.stage.Screen;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class WindowsUtil {
|
||||||
|
// 获取系统缩放比
|
||||||
|
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 Stage getStage() {
|
||||||
|
return Application.getPrimaryStage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stage getStage(Pane pane) {
|
||||||
|
try {
|
||||||
|
return (Stage) pane.getScene().getWindow();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return getStage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/main/java/cn/octopusyan/alistgui/view/PopupMenu.java
Normal file
137
src/main/java/cn/octopusyan/alistgui/view/PopupMenu.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package cn.octopusyan.alistgui.view;
|
||||||
|
|
||||||
|
import atlantafx.base.controls.CaptionMenuItem;
|
||||||
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
|
import cn.octopusyan.alistgui.util.WindowsUtil;
|
||||||
|
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() / WindowsUtil.scaleX,
|
||||||
|
event.getY() / WindowsUtil.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
package cn.octopusyan.alistgui.view.alert;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.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 static Window mOwner;
|
||||||
|
|
||||||
|
public static void initOwner(Stage stage) {
|
||||||
|
AlertUtil.mOwner = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DefaultBuilder builder() {
|
||||||
|
return new DefaultBuilder(mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertBuilder info(String content) {
|
||||||
|
return info().content(content).header(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertBuilder info() {
|
||||||
|
return alert(Alert.AlertType.INFORMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertBuilder error(String message) {
|
||||||
|
return alert(Alert.AlertType.ERROR).header(null).content(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertBuilder warning() {
|
||||||
|
return alert(Alert.AlertType.WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertBuilder exception(Exception ex) {
|
||||||
|
return alert(Alert.AlertType.ERROR).exception(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认对话框
|
||||||
|
*/
|
||||||
|
public static AlertBuilder confirm() {
|
||||||
|
return alert(Alert.AlertType.CONFIRMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义确认对话框 <p>
|
||||||
|
*
|
||||||
|
* @param buttons <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
|
||||||
|
*/
|
||||||
|
public static AlertBuilder confirm(String... buttons) {
|
||||||
|
return confirm().buttons(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertBuilder confirm(ButtonType... buttons) {
|
||||||
|
return confirm().buttons(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertBuilder alert(Alert.AlertType type) {
|
||||||
|
return new AlertBuilder(mOwner, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextInputBuilder input(String content) {
|
||||||
|
return new TextInputBuilder(mOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextInputBuilder input(String content, String defaultResult) {
|
||||||
|
return new TextInputBuilder(mOwner, defaultResult).content(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> ChoiceBuilder<T> choices(String hintText, T... choices) {
|
||||||
|
return new ChoiceBuilder<>(mOwner, choices).content(hintText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static 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,109 @@
|
|||||||
|
package cn.octopusyan.alistgui.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseBuilder;
|
||||||
|
import cn.octopusyan.alistgui.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)) || "取消".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.alistgui.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.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,50 @@
|
|||||||
|
package cn.octopusyan.alistgui.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseBuilder;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.util.WindowsUtil;
|
||||||
|
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) {
|
||||||
|
super(new Dialog<>(), mOwner);
|
||||||
|
|
||||||
|
header(null);
|
||||||
|
|
||||||
|
DialogPane dialogPane = dialog.getDialogPane();
|
||||||
|
dialogPane.getScene().setFill(Color.TRANSPARENT);
|
||||||
|
WindowsUtil.bindDragged(dialogPane);
|
||||||
|
WindowsUtil.bindShadow(dialogPane);
|
||||||
|
WindowsUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT);
|
||||||
|
|
||||||
|
dialogPane.getButtonTypes().add(new ButtonType(
|
||||||
|
Context.getLanguageBinding("label.cancel").get(),
|
||||||
|
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,52 @@
|
|||||||
|
package cn.octopusyan.alistgui.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
public ProgressBuilder(Window mOwner) {
|
||||||
|
super(mOwner);
|
||||||
|
content(getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pane getContent() {
|
||||||
|
HBox hBox = new HBox();
|
||||||
|
hBox.setPrefWidth(350);
|
||||||
|
hBox.setAlignment(Pos.CENTER);
|
||||||
|
hBox.setPadding(new Insets(10, 0, 10, 0));
|
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
Button cancel = new Button(Context.getLanguageBinding("label.cancel").get());
|
||||||
|
cancel.setCancelButton(true);
|
||||||
|
cancel.setOnAction(_ -> dialog.close());
|
||||||
|
|
||||||
|
// 进度条 TODO 宽度绑定
|
||||||
|
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.alistgui.view.alert.builder;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.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,138 @@
|
|||||||
|
package cn.octopusyan.alistgui.viewModel;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseViewModel;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConsoleLog;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.AList;
|
||||||
|
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
|
||||||
|
import cn.octopusyan.alistgui.task.CheckUpdateTask;
|
||||||
|
import cn.octopusyan.alistgui.task.listener.TaskListener;
|
||||||
|
import cn.octopusyan.alistgui.util.DownloadUtil;
|
||||||
|
import cn.octopusyan.alistgui.view.alert.AlertUtil;
|
||||||
|
import cn.octopusyan.alistgui.view.alert.builder.AlertBuilder;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关于
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class AboutViewModule extends BaseViewModel {
|
||||||
|
private final StringProperty aListVersion = new SimpleStringProperty(ConfigManager.aListVersion());
|
||||||
|
private final StringProperty aListNewVersion = new SimpleStringProperty("");
|
||||||
|
private final BooleanProperty aListUpgrade = new SimpleBooleanProperty(false);
|
||||||
|
private final StringProperty guiVersion = new SimpleStringProperty(ConfigManager.guiVersion());
|
||||||
|
private final StringProperty guiNewVersion = new SimpleStringProperty("");
|
||||||
|
private final BooleanProperty guiUpgrade = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
|
public AboutViewModule() {
|
||||||
|
aListVersion.bindBidirectional(ConfigManager.aListVersionProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Property<String> aListVersionProperty() {
|
||||||
|
return aListVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty aListUpgradeProperty() {
|
||||||
|
return aListUpgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty aListNewVersionProperty() {
|
||||||
|
return aListNewVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty guiVersionProperty() {
|
||||||
|
return guiVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty guiNewVersionProperty() {
|
||||||
|
return guiNewVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty guiUpgradeProperty() {
|
||||||
|
return guiUpgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查更新
|
||||||
|
*/
|
||||||
|
public void checkUpdate(UpgradeApp app) {
|
||||||
|
|
||||||
|
// 检查任务
|
||||||
|
startUpgrade(app, () -> {
|
||||||
|
// 判断 检查的应用
|
||||||
|
boolean tag = app instanceof AList;
|
||||||
|
|
||||||
|
boolean upgrade = tag ? aListUpgrade.get() : guiUpgrade.get();
|
||||||
|
String version = tag ? aListVersion.get() : guiVersion.get();
|
||||||
|
String newVersion = tag ? aListNewVersion.get() : guiNewVersion.get();
|
||||||
|
String title = Context.getLanguageBinding(STR."about.\{tag ? "alist" : "app"}.update").getValue();
|
||||||
|
String currentLabel = Context.getLanguageBinding("update.current").get();
|
||||||
|
String newLabel = Context.getLanguageBinding("update.remote").get();
|
||||||
|
String header = Context.getLanguageBinding(STR."update.upgrade.\{upgrade ? "new" : "not"}").get();
|
||||||
|
|
||||||
|
// 版本检查消息
|
||||||
|
String msg = STR."\{app.getRepo()}\{upgrade ? "" : STR." \{version}"} \{header} \{upgrade ? newVersion : ""}";
|
||||||
|
log.info(msg);
|
||||||
|
ConsoleLog.info(msg);
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
AlertBuilder builder = upgrade ? AlertUtil.confirm() : AlertUtil.info();
|
||||||
|
builder.title(title)
|
||||||
|
.header(header)
|
||||||
|
.content(STR."""
|
||||||
|
\{currentLabel} : \{version}
|
||||||
|
\{newLabel} : \{newVersion}
|
||||||
|
""")
|
||||||
|
.show(() -> {
|
||||||
|
// 可升级,且点击了确定后,开始下载任务
|
||||||
|
if (upgrade)
|
||||||
|
DownloadUtil.startDownload(app, newVersion, () -> {
|
||||||
|
// 下载完成后,解压并删除文件
|
||||||
|
DownloadUtil.unzip(app);
|
||||||
|
// 设置应用版本
|
||||||
|
Platform.runLater(() -> aListVersion.setValue(aListNewVersion.getValue()));
|
||||||
|
}).execute();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startUpgrade(UpgradeApp app, Runnable runnable) {
|
||||||
|
// 检查更新的任务
|
||||||
|
var task = new CheckUpdateTask(app);
|
||||||
|
|
||||||
|
// 任务监听
|
||||||
|
task.onListen(new TaskListener.UpgradeUpgradeListener(task) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSucceed() {
|
||||||
|
if (runnable != null) runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChecked(boolean hasUpgrade, String version) {
|
||||||
|
// 版本检查结果
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (app instanceof AList) {
|
||||||
|
aListUpgrade.setValue(hasUpgrade);
|
||||||
|
aListNewVersion.setValue(version);
|
||||||
|
} else {
|
||||||
|
guiUpgrade.setValue(hasUpgrade);
|
||||||
|
guiNewVersion.setValue(version);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFail(Throwable throwable) {
|
||||||
|
AlertUtil.exception(new Exception(throwable)).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 执行任务
|
||||||
|
task.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package cn.octopusyan.alistgui.viewModel;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseViewModel;
|
||||||
|
import cn.octopusyan.alistgui.manager.AListManager;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin Panel VM
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class AdminPanelViewModel extends BaseViewModel {
|
||||||
|
private final StringProperty password = new SimpleStringProperty(AListManager.passwordProperty().get());
|
||||||
|
|
||||||
|
public AdminPanelViewModel() {
|
||||||
|
AListManager.passwordProperty().subscribe(password::set);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty passwordProperty() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package cn.octopusyan.alistgui.viewModel;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseViewModel;
|
||||||
|
import cn.octopusyan.alistgui.manager.AListManager;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主界面VM
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class MainViewModel extends BaseViewModel {
|
||||||
|
private final BooleanProperty running = new SimpleBooleanProperty();
|
||||||
|
|
||||||
|
public MainViewModel() {
|
||||||
|
// 先添加监听再绑定,解决切换locale后,界面状态显示错误的问题
|
||||||
|
running.bind(AListManager.runningProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty runningProperty() {
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package cn.octopusyan.alistgui.viewModel;
|
||||||
|
|
||||||
|
import cn.octopusyan.alistgui.base.BaseViewModel;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root VM
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
public class RootViewModel extends BaseViewModel {
|
||||||
|
private final IntegerProperty currentViewIndex = new SimpleIntegerProperty(Context.currentViewIndex()) {
|
||||||
|
{
|
||||||
|
Context.currentViewIndexProperty().bind(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public IntegerProperty currentViewIndexProperty() {
|
||||||
|
return currentViewIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
package cn.octopusyan.alistgui.viewModel;
|
||||||
|
|
||||||
|
import atlantafx.base.theme.Theme;
|
||||||
|
import cn.octopusyan.alistgui.base.BaseViewModel;
|
||||||
|
import cn.octopusyan.alistgui.config.Constants;
|
||||||
|
import cn.octopusyan.alistgui.config.Context;
|
||||||
|
import cn.octopusyan.alistgui.enums.ProxySetup;
|
||||||
|
import cn.octopusyan.alistgui.manager.ConfigManager;
|
||||||
|
import cn.octopusyan.alistgui.manager.http.HttpUtil;
|
||||||
|
import cn.octopusyan.alistgui.task.ProxyCheckTask;
|
||||||
|
import cn.octopusyan.alistgui.task.listener.TaskListener;
|
||||||
|
import cn.octopusyan.alistgui.util.Registry;
|
||||||
|
import cn.octopusyan.alistgui.view.alert.AlertUtil;
|
||||||
|
import javafx.beans.property.*;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置视图数据
|
||||||
|
*
|
||||||
|
* @author octopus_yan
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SetupViewModel extends BaseViewModel {
|
||||||
|
private final BooleanProperty autoStart = new SimpleBooleanProperty(ConfigManager.autoStart());
|
||||||
|
private final BooleanProperty silentStartup = new SimpleBooleanProperty(ConfigManager.silentStartup());
|
||||||
|
private final BooleanProperty closeToTray = new SimpleBooleanProperty(ConfigManager.closeToTray());
|
||||||
|
private final ObjectProperty<Theme> theme = new SimpleObjectProperty<>(ConfigManager.theme());
|
||||||
|
private final StringProperty proxyHost = new SimpleStringProperty(ConfigManager.proxyHost());
|
||||||
|
private final StringProperty proxyPort = new SimpleStringProperty(ConfigManager.proxyPort());
|
||||||
|
private final ObjectProperty<Locale> language = new SimpleObjectProperty<>(ConfigManager.language());
|
||||||
|
private final ObjectProperty<ProxySetup> proxySetup = new SimpleObjectProperty<>(ConfigManager.proxySetup());
|
||||||
|
private final StringProperty proxyTestUrl = new SimpleStringProperty(ConfigManager.proxyTestUrl());
|
||||||
|
|
||||||
|
|
||||||
|
public SetupViewModel() {
|
||||||
|
theme.addListener((_, _, newValue) -> ConfigManager.theme(newValue));
|
||||||
|
silentStartup.addListener((_, _, newValue) -> ConfigManager.silentStartup(newValue));
|
||||||
|
autoStart.addListener((_, _, newValue) -> {
|
||||||
|
try {
|
||||||
|
if (newValue) {
|
||||||
|
Registry.setStringValue(Registry.Root.HKCU, Constants.REG_AUTO_RUN, Constants.APP_TITLE, Constants.APP_EXE);
|
||||||
|
} else {
|
||||||
|
Registry.deleteValue(Registry.Root.HKCU, Constants.REG_AUTO_RUN, Constants.APP_TITLE);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
log.error("", e);
|
||||||
|
}
|
||||||
|
ConfigManager.autoStart(newValue);
|
||||||
|
});
|
||||||
|
silentStartup.addListener((_, _, newValue) -> {
|
||||||
|
// 开启时检查托盘选项
|
||||||
|
if (newValue && !closeToTray.get()) closeToTray.set(true);
|
||||||
|
|
||||||
|
ConfigManager.silentStartup(newValue);
|
||||||
|
});
|
||||||
|
closeToTray.addListener((_, _, newValue) -> {
|
||||||
|
// 开启时检查托盘选项
|
||||||
|
if (!newValue && silentStartup.get()) silentStartup.set(false);
|
||||||
|
|
||||||
|
ConfigManager.closeToTray(newValue);
|
||||||
|
});
|
||||||
|
proxySetup.addListener((_, _, newValue) -> ConfigManager.proxySetup(newValue));
|
||||||
|
proxyTestUrl.addListener((_, _, newValue) -> ConfigManager.proxyTestUrl(newValue));
|
||||||
|
proxyHost.addListener((_, _, newValue) -> ConfigManager.proxyHost(newValue));
|
||||||
|
proxyPort.addListener((_, _, newValue) -> ConfigManager.proxyPort(newValue));
|
||||||
|
language.addListener((_, _, newValue) -> Context.setLanguage(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Theme> themeProperty() {
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty autoStartProperty() {
|
||||||
|
return autoStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty silentStartupProperty() {
|
||||||
|
return silentStartup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty closeToTrayProperty() {
|
||||||
|
return closeToTray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<Locale> languageProperty() {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectProperty<ProxySetup> proxySetupProperty() {
|
||||||
|
return proxySetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty proxyHostProperty() {
|
||||||
|
return proxyHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty proxyPortProperty() {
|
||||||
|
return proxyPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void proxyTest() {
|
||||||
|
var checkUrl = AlertUtil.input("URL :", proxyTestUrl.getValue())
|
||||||
|
.title(Context.getLanguageBinding("proxy.test.title").getValue())
|
||||||
|
.header(Context.getLanguageBinding("proxy.test.header").getValue())
|
||||||
|
.getInput();
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(checkUrl)) return;
|
||||||
|
|
||||||
|
proxyTestUrl.setValue(checkUrl);
|
||||||
|
|
||||||
|
ConfigManager.checkProxy((success, msg) -> {
|
||||||
|
if (!success) {
|
||||||
|
final var tmp = Context.getLanguageBinding("proxy.test.result.failed").getValue();
|
||||||
|
AlertUtil.error(STR."\{tmp}\{msg}").show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpUtil.getInstance().proxy(ConfigManager.proxySetup(), ConfigManager.getProxyInfo());
|
||||||
|
getProxyCheckTask(checkUrl).execute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProxyCheckTask getProxyCheckTask(String checkUrl) {
|
||||||
|
var task = new ProxyCheckTask(checkUrl);
|
||||||
|
task.onListen(new TaskListener(task) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSucceed() {
|
||||||
|
AlertUtil.info(Context.getLanguageBinding("proxy.test.result.success").getValue()).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFail(Throwable throwable) {
|
||||||
|
final var tmp = Context.getLanguageBinding("proxy.test.result.failed").getValue();
|
||||||
|
String throwableMessage = throwable.getMessage();
|
||||||
|
AlertUtil.error(tmp + (StringUtils.isEmpty(throwableMessage) ? "" : throwableMessage)).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,27 @@
|
|||||||
module cn.octopusyan.alistgui {
|
module cn.octopusyan.alistgui {
|
||||||
|
requires java.desktop;
|
||||||
requires java.net.http;
|
requires java.net.http;
|
||||||
requires javafx.controls;
|
requires javafx.controls;
|
||||||
requires javafx.fxml;
|
requires javafx.fxml;
|
||||||
requires javafx.graphics;
|
requires javafx.graphics;
|
||||||
requires org.apache.commons.io;
|
|
||||||
requires org.apache.commons.lang3;
|
requires org.apache.commons.lang3;
|
||||||
requires org.apache.commons.exec;
|
requires org.apache.commons.exec;
|
||||||
requires org.slf4j;
|
requires org.slf4j;
|
||||||
requires ch.qos.logback.core;
|
requires ch.qos.logback.core;
|
||||||
requires ch.qos.logback.classic;
|
requires ch.qos.logback.classic;
|
||||||
requires com.alibaba.fastjson2;
|
requires cn.hutool.core;
|
||||||
|
requires org.kordamp.ikonli.javafx;
|
||||||
|
requires org.kordamp.ikonli.fontawesome;
|
||||||
|
requires com.gluonhq.emoji;
|
||||||
|
requires static lombok;
|
||||||
|
requires com.fasterxml.jackson.databind;
|
||||||
|
requires com.fasterxml.jackson.dataformat.yaml;
|
||||||
|
requires atlantafx.base;
|
||||||
|
|
||||||
exports cn.octopusyan.alistgui;
|
exports cn.octopusyan.alistgui;
|
||||||
opens cn.octopusyan.alistgui to javafx.fxml;
|
opens cn.octopusyan.alistgui to javafx.fxml;
|
||||||
|
opens cn.octopusyan.alistgui.model to com.fasterxml.jackson.databind;
|
||||||
opens cn.octopusyan.alistgui.controller to javafx.fxml;
|
opens cn.octopusyan.alistgui.controller to javafx.fxml;
|
||||||
|
opens cn.octopusyan.alistgui.base to com.fasterxml.jackson.databind;
|
||||||
|
opens cn.octopusyan.alistgui.model.upgrade to com.fasterxml.jackson.databind;
|
||||||
}
|
}
|
||||||
@ -1,3 +1,3 @@
|
|||||||
app.name=${project.name}
|
app.name=${project.name}
|
||||||
app.title=alist gui
|
app.title=AList GUI
|
||||||
app.version=${project.version}
|
app.version=${project.version}
|
||||||
BIN
src/main/resources/assets/logo-about.png
Normal file
BIN
src/main/resources/assets/logo-about.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
BIN
src/main/resources/assets/logo-disabled.png
Normal file
BIN
src/main/resources/assets/logo-disabled.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 960 B |
BIN
src/main/resources/assets/logo.png
Normal file
BIN
src/main/resources/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
14
src/main/resources/assets/logo.svg
Normal file
14
src/main/resources/assets/logo.svg
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<svg width="1252" height="1252" xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||||
|
<g>
|
||||||
|
<g id="#70c6beff">
|
||||||
|
<path id="svg_2"
|
||||||
|
d="m634.37,138.38c11.88,-1.36 24.25,1.3 34.18,8.09c14.96,9.66 25.55,24.41 34.49,39.51c40.59,68.03 81.45,135.91 122.02,203.96c54.02,90.99 108.06,181.97 161.94,273.06c37.28,63 74.65,125.96 112.18,188.82c24.72,41.99 50.21,83.54 73.84,126.16c10.18,17.84 15.77,38.44 14.93,59.03c-0.59,15.92 -3.48,32.28 -11.84,46.08c-11.73,19.46 -31.39,33.2 -52.71,40.36c-11.37,4.09 -23.3,6.87 -35.43,6.89c-132.32,-0.05 -264.64,0.04 -396.95,0.03c-11.38,-0.29 -22.95,-1.6 -33.63,-5.72c-7.81,-3.33 -15.5,-7.43 -21.61,-13.42c-10.43,-10.32 -17.19,-24.96 -15.38,-39.83c0.94,-10.39 3.48,-20.64 7.76,-30.16c4.15,-9.77 9.99,-18.67 15.06,-27.97c22.13,-39.47 45.31,-78.35 69.42,-116.65c7.72,-12.05 14.44,-25.07 25.12,-34.87c11.35,-10.39 25.6,-18.54 41.21,-19.6c12.55,-0.52 24.89,3.82 35.35,10.55c11.8,6.92 21.09,18.44 24.2,31.88c4.49,17.01 -0.34,34.88 -7.55,50.42c-8.09,17.65 -19.62,33.67 -25.81,52.18c-1.13,4.21 -2.66,9.52 0.48,13.23c3.19,3 7.62,4.18 11.77,5.22c12,2.67 24.38,1.98 36.59,2.06c45,-0.01 90,0 135,0c8.91,-0.15 17.83,0.3 26.74,-0.22c6.43,-0.74 13.44,-1.79 18.44,-6.28c3.3,-2.92 3.71,-7.85 2.46,-11.85c-2.74,-8.86 -7.46,-16.93 -12.12,-24.89c-119.99,-204.91 -239.31,-410.22 -360.56,-614.4c-3.96,-6.56 -7.36,-13.68 -13.03,-18.98c-2.8,-2.69 -6.95,-4.22 -10.77,-3.11c-3.25,1.17 -5.45,4.03 -7.61,6.57c-5.34,6.81 -10.12,14.06 -14.51,21.52c-20.89,33.95 -40.88,68.44 -61.35,102.64c-117.9,198.43 -235.82,396.85 -353.71,595.29c-7.31,13.46 -15.09,26.67 -23.57,39.43c-7.45,10.96 -16.49,21.23 -28.14,27.83c-13.73,7.94 -30.69,11.09 -46.08,6.54c-11.23,-3.47 -22.09,-9.12 -30.13,-17.84c-10.18,-10.08 -14.69,-24.83 -14.17,-38.94c0.52,-14.86 5.49,-29.34 12.98,-42.1c71.58,-121.59 143.62,-242.92 215.93,-364.09c37.2,-62.8 74.23,-125.69 111.64,-188.36c37.84,-63.5 75.77,-126.94 113.44,-190.54c21.02,-35.82 42.19,-71.56 64.28,-106.74c6.79,-11.15 15.58,-21.15 26.16,-28.85c8.68,-5.92 18.42,-11 29.05,-11.94z"
|
||||||
|
fill="#70c6be"/>
|
||||||
|
</g>
|
||||||
|
<g id="#1ba0d8ff">
|
||||||
|
<path id="svg_3"
|
||||||
|
d="m628.35,608.38c17.83,-2.87 36.72,1.39 51.5,11.78c11.22,8.66 19.01,21.64 21.26,35.65c1.53,10.68 0.49,21.75 -3.44,31.84c-3.02,8.73 -7.35,16.94 -12.17,24.81c-68.76,115.58 -137.5,231.17 -206.27,346.75c-8.8,14.47 -16.82,29.47 -26.96,43.07c-7.37,9.11 -16.58,16.85 -27.21,21.89c-22.47,11.97 -51.79,4.67 -68.88,-13.33c-8.66,-8.69 -13.74,-20.63 -14.4,-32.84c-0.98,-12.64 1.81,-25.42 7.53,-36.69c5.03,-10.96 10.98,-21.45 17.19,-31.77c30.22,-50.84 60.17,-101.84 90.3,-152.73c41.24,-69.98 83.16,-139.55 124.66,-209.37c4.41,-7.94 9.91,-15.26 16.09,-21.9c8.33,-8.46 18.9,-15.3 30.8,-17.16z"
|
||||||
|
fill="#1ba0d8"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src/main/resources/assets/windows/alist-gui.ico
Normal file
BIN
src/main/resources/assets/windows/alist-gui.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
22
src/main/resources/css/about-view.scss
Normal file
22
src/main/resources/css/about-view.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/**************************************************
|
||||||
|
* About View
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
.shield {
|
||||||
|
|
||||||
|
.label {
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-label-padding: 3 5 3 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shield-name {
|
||||||
|
-fx-background-color: #555555;
|
||||||
|
-fx-background-radius: 5 0 0 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shield-version {
|
||||||
|
-fx-background-color: #6969AA;
|
||||||
|
-fx-background-radius: 0 5 5 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/resources/css/admin-panel.scss
Normal file
31
src/main/resources/css/admin-panel.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**************************************************
|
||||||
|
* Admin Password Panel
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
#admin-panel {
|
||||||
|
-fx-background-color: -color-bg-default;
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
-fx-border-radius: 15;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
.label {
|
||||||
|
-fx-font-size: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button, .button.ikonli-font-icon {
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-field {
|
||||||
|
-fx-spacing: 10;
|
||||||
|
|
||||||
|
.text-field {
|
||||||
|
-fx-pref-width: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-value {
|
||||||
|
//-fx-padding: 5 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/main/resources/css/main-view.scss
Normal file
71
src/main/resources/css/main-view.scss
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**************************************************
|
||||||
|
* Main View
|
||||||
|
**************************************************/
|
||||||
|
#homeLabel {
|
||||||
|
-fx-font-size: 35;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#statusLabel {
|
||||||
|
-fx-padding: 2 5 2 5;
|
||||||
|
-fx-font-size: 15;
|
||||||
|
-fx-background-radius: 10;
|
||||||
|
-fx-text-alignment: CENTER;
|
||||||
|
-fx-border-radius: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-menu, #moreButton {
|
||||||
|
-fx-font-size: 15;
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
-fx-padding: 10 40;
|
||||||
|
-fx-border-radius: 15;
|
||||||
|
-fx-border-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#startButton {
|
||||||
|
-color-button-bg-focused: -color-button-bg;
|
||||||
|
-fx-border-color: -color-button-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
#passwordButton {
|
||||||
|
-color-button-bg: -color-success-3;
|
||||||
|
-color-button-bg-hover: -color-button-bg;
|
||||||
|
-color-button-bg-focused: -color-button-bg;
|
||||||
|
-color-button-bg-pressed: -color-button-bg;
|
||||||
|
-fx-border-color: -color-button-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restartButton {
|
||||||
|
-color-button-bg: linear-gradient(to bottom right, -color-accent-3, -color-chart-6);
|
||||||
|
-color-button-bg-hover: -color-button-bg;
|
||||||
|
-color-button-bg-focused: -color-button-bg;
|
||||||
|
-color-button-bg-pressed: -color-button-bg;
|
||||||
|
-fx-border-color: -color-button-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
#moreButton {
|
||||||
|
-fx-padding: 3 30;
|
||||||
|
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-color: -color-chart-6-alpha70;
|
||||||
|
-color-button-fg: -color-chart-6-alpha70;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
-fx-background-color: -color-chart-6-alpha20;
|
||||||
|
-fx-border-color: -color-chart-6-alpha70;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu, .menu-item {
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
-fx-border-radius: 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logArea {
|
||||||
|
-fx-font-family: "Lucida Console";
|
||||||
|
-fx-font-size: 15;
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
-fx-border-radius: 15;
|
||||||
|
-fx-padding: 5 15 5 15;
|
||||||
|
-fx-background-color: -color-neutral-muted;
|
||||||
|
}
|
||||||
@ -1,176 +0,0 @@
|
|||||||
@import "root.css";
|
|
||||||
|
|
||||||
/**************************************************
|
|
||||||
* Window Header
|
|
||||||
**************************************************/
|
|
||||||
#windowHeader .iconButton {
|
|
||||||
/*-fx-max-height: 5px;*/
|
|
||||||
/*-fx-max-width: 5px;*/
|
|
||||||
-fx-border-radius: 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rootPane #closeIcon {
|
|
||||||
-fx-color: #fa6057;
|
|
||||||
-fx-opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rootPane #closeIcon:hover {
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rootPane #minimizeIcon {
|
|
||||||
-fx-color: #fbbc2e;
|
|
||||||
-fx-opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rootPane #minimizeIcon:hover {
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rootPane #alwaysOnTopIcon {
|
|
||||||
-fx-color: #27c940;
|
|
||||||
-fx-opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rootPane #alwaysOnTopIcon:hover {
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rootPane #alwaysOnTopIcon:always-on-top {
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************
|
|
||||||
* Tab label
|
|
||||||
**************************************************/
|
|
||||||
|
|
||||||
#tabPane .tab-header-area {
|
|
||||||
-fx-background-radius: 10;
|
|
||||||
-fx-background-color: #0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabPane .headers-region {
|
|
||||||
-fx-alignment: TOP_CENTER;
|
|
||||||
-fx-background-color: #18181a;
|
|
||||||
-fx-background-radius: 10;
|
|
||||||
-fx-padding: 5 0 5 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabPane .tab-header-background {
|
|
||||||
-fx-background-color: #0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabPane .tab {
|
|
||||||
-fx-text-fill: white;
|
|
||||||
-fx-padding: 10 20 10 20;
|
|
||||||
-fx-background-radius: 10;
|
|
||||||
-fx-background-color: #0000;
|
|
||||||
-fx-border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabPane .tab-label {
|
|
||||||
-fx-font-size: 15px;
|
|
||||||
-fx-text-fill: #707079;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabPane .tab:selected {
|
|
||||||
-fx-background-color: #2c69e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabPane .tab:selected .tab-label {
|
|
||||||
-fx-text-fill: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************************
|
|
||||||
* Main View
|
|
||||||
**************************************************/
|
|
||||||
#homeLabel {
|
|
||||||
-fx-font-size: 35px;
|
|
||||||
-fx-font-weight: bold;
|
|
||||||
-fx-text-fill: white;
|
|
||||||
-fx-font-family: 'JetBrains Mono';
|
|
||||||
}
|
|
||||||
|
|
||||||
#statusLabel {
|
|
||||||
-fx-padding: 2 5 2 5;
|
|
||||||
-fx-text-fill: black;
|
|
||||||
-fx-background-color: #1bc964;
|
|
||||||
-fx-background-radius: 10;
|
|
||||||
-fx-text-alignment: CENTER;
|
|
||||||
-fx-border-radius: 10;
|
|
||||||
-fx-border-color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controlMenu {
|
|
||||||
-fx-font-size: 15;
|
|
||||||
-fx-background-radius: 10px;
|
|
||||||
-fx-padding: 10 40 10 40;
|
|
||||||
-fx-border-radius: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controlMenu:focused {
|
|
||||||
-fx-opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
#startButton {
|
|
||||||
-fx-background-color: #fa6057;
|
|
||||||
-fx-text-fill: white;
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#passwordButton {
|
|
||||||
-fx-background-color: #1bc964;
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#restartButton {
|
|
||||||
-fx-background-color: linear-gradient(#57b4f2, #9198e5);
|
|
||||||
-fx-text-fill: white;
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#moreButton {
|
|
||||||
-fx-background-color: #0000;
|
|
||||||
-fx-text-fill: #9254d1;
|
|
||||||
-fx-border-color: #9254d1;
|
|
||||||
-fx-border-width: 2px;
|
|
||||||
-fx-opacity: 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#tabPane .tab:selected .focus-indicator {
|
|
||||||
-fx-border-width: 0;
|
|
||||||
-fx-border-color: #0000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logArea {
|
|
||||||
-fx-text-fill: #e3e4e4;
|
|
||||||
-fx-font-family: 'JetBrains Mono';
|
|
||||||
-fx-font-size: 20;
|
|
||||||
-fx-background-radius: 15;
|
|
||||||
-fx-border-radius: 15;
|
|
||||||
-fx-border-color: transparent;
|
|
||||||
-fx-background-insets: 0;
|
|
||||||
-fx-background-color: #18181c;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logArea .content {
|
|
||||||
-fx-padding: 15;
|
|
||||||
-fx-background-radius: 15;
|
|
||||||
-fx-background-color: #18181c;
|
|
||||||
-fx-border-radius: 15;
|
|
||||||
-fx-border-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logArea .scroll-pane {
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
}
|
|
||||||
#logArea .scroll-pane .viewport{
|
|
||||||
-fx-background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#logArea:focused {
|
|
||||||
-fx-background-radius: 15;
|
|
||||||
-fx-border-radius: 15;
|
|
||||||
-fx-border-color: transparent;
|
|
||||||
-fx-background-color: #18181c;
|
|
||||||
}
|
|
||||||
112
src/main/resources/css/root-view.scss
Normal file
112
src/main/resources/css/root-view.scss
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
@import "root.css";
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Window Header
|
||||||
|
**************************************************/
|
||||||
|
#windowHeader {
|
||||||
|
.icon-button {
|
||||||
|
-fx-icon-code: fa-circle;
|
||||||
|
-fx-opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
-fx-opacity: 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#closeIcon {
|
||||||
|
-fx-icon-color: -color-chart-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#minimizeIcon {
|
||||||
|
-fx-icon-color: -color-chart-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#alwaysOnTopIcon {
|
||||||
|
-fx-icon-color: -color-chart-3;
|
||||||
|
|
||||||
|
&:always-on-top {
|
||||||
|
-fx-opacity: 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Tab label
|
||||||
|
**************************************************/
|
||||||
|
#tabPane {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
|
||||||
|
.tab-header-area {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
|
||||||
|
.tab-header-background {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headers-region {
|
||||||
|
//-fx-background-color: #f9f9fb;
|
||||||
|
-fx-background-color: -color-neutral-muted;
|
||||||
|
-fx-background-radius: 10;
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
-fx-padding: 5;
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
|
||||||
|
.tab-container {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-border-width: 0;
|
||||||
|
|
||||||
|
.tab-label {
|
||||||
|
-fx-pref-width: 80;
|
||||||
|
-fx-padding: 10 0;
|
||||||
|
-fx-background-radius: 10;
|
||||||
|
-fx-text-alignment: CENTER;
|
||||||
|
-fx-alignment: CENTER;
|
||||||
|
-fx-font-size: 15px;
|
||||||
|
-fx-border-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:selected {
|
||||||
|
|
||||||
|
.tab-label {
|
||||||
|
-fx-background-color: -color-accent-5;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ikonli-font-icon {
|
||||||
|
-fx-icon-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Window Footer
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
#windowFooter {
|
||||||
|
.button {
|
||||||
|
-fx-font-size: 15;
|
||||||
|
-fx-text-alignment: CENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ikonli-font-icon {
|
||||||
|
-fx-font-size: 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************
|
||||||
|
* Modal Pane
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
.modal-pane {
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
|
||||||
|
.scrollable-content {
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
/**************************************************
|
|
||||||
* Root
|
|
||||||
**************************************************/
|
|
||||||
|
|
||||||
.rootPane {
|
|
||||||
-fx-background-color: black;
|
|
||||||
-fx-background-radius: 10;
|
|
||||||
-fx-border-radius: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root {
|
|
||||||
-fx-font-family: "Comic Sans MS";
|
|
||||||
}
|
|
||||||
|
|
||||||
20
src/main/resources/css/root.scss
Normal file
20
src/main/resources/css/root.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**************************************************
|
||||||
|
* Root
|
||||||
|
**************************************************/
|
||||||
|
.root {
|
||||||
|
-fx-font-size: 15;
|
||||||
|
-fx-font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-pane {
|
||||||
|
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
-fx-border-radius: 15;
|
||||||
|
|
||||||
|
// 窗口阴影
|
||||||
|
//-fx-background-color: rgba(255, 255, 255, 1);
|
||||||
|
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.3), 15, 0, 0, 0);
|
||||||
|
-fx-background-insets: 20;
|
||||||
|
-fx-padding: 20;
|
||||||
|
}
|
||||||
|
|
||||||
27
src/main/resources/css/setup-view.scss
Normal file
27
src/main/resources/css/setup-view.scss
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**************************************************
|
||||||
|
* Setup View
|
||||||
|
**************************************************/
|
||||||
|
|
||||||
|
#setupView {
|
||||||
|
.check-box {
|
||||||
|
-fx-font-size: 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-panel {
|
||||||
|
-fx-background-color: -color-neutral-muted;
|
||||||
|
-fx-background-radius: 15;
|
||||||
|
-fx-border-radius: 15;
|
||||||
|
-fx-border-width: 5;
|
||||||
|
|
||||||
|
.radio-button {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-label {
|
||||||
|
-fx-font-size: 15;
|
||||||
|
-fx-text-fill: -color-accent-5;
|
||||||
|
-fx-text-alignment: CENTER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
34
src/main/resources/fxml/about-view.fxml
Normal file
34
src/main/resources/fxml/about-view.fxml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<VBox fx:id="aboutView" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
|
||||||
|
prefHeight="700" prefWidth="720" spacing="30" alignment="CENTER"
|
||||||
|
stylesheets="@../css/about-view.css"
|
||||||
|
fx:controller="cn.octopusyan.alistgui.controller.AboutController">
|
||||||
|
|
||||||
|
<StackPane>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10"/>
|
||||||
|
</padding>
|
||||||
|
<ImageView pickOnBounds="true" preserveRatio="true">
|
||||||
|
<Image url="@../assets/logo-about.png" backgroundLoading="true"/>
|
||||||
|
</ImageView>
|
||||||
|
</StackPane>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER" styleClass="shield">
|
||||||
|
<Label fx:id="aListVersionLabel" styleClass="shield-name" text="%about.alist.version"/>
|
||||||
|
<Label fx:id="aListVersion" styleClass="shield-version"/>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="CENTER" styleClass="shield">
|
||||||
|
<Label fx:id="appVersionLabel" styleClass="shield-name" text="%about.app.version"/>
|
||||||
|
<Label styleClass="shield-version" text="v${project.version}"/>
|
||||||
|
</HBox>
|
||||||
|
<Button fx:id="checkAppVersion" onAction="#checkGuiUpdate" styleClass="flat" text="%about.app.update"/>
|
||||||
|
<Button fx:id="checkAListVersion" onAction="#checkAListUpdate" styleClass="flat" text="%about.alist.update"/>
|
||||||
|
</VBox>
|
||||||
65
src/main/resources/fxml/admin-panel.fxml
Normal file
65
src/main/resources/fxml/admin-panel.fxml
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import atlantafx.base.layout.InputGroup?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import org.kordamp.ikonli.javafx.*?>
|
||||||
|
<AnchorPane id="admin-panel" fx:id="adminPanel" maxHeight="250" maxWidth="520" prefHeight="250.0" prefWidth="520.0"
|
||||||
|
stylesheets="@../css/admin-panel.css" xmlns="http://javafx.com/javafx/11.0.14-internal"
|
||||||
|
xmlns:fx="http://javafx.com/fxml/1" fx:controller="cn.octopusyan.alistgui.controller.PasswordController">
|
||||||
|
|
||||||
|
<AnchorPane styleClass="header" prefWidth="520" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0"
|
||||||
|
AnchorPane.topAnchor="0">
|
||||||
|
<Label text="%admin.pwd.title" AnchorPane.leftAnchor="10" AnchorPane.topAnchor="10"/>
|
||||||
|
<Button onAction="#close" styleClass="flat" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0">
|
||||||
|
<graphic>
|
||||||
|
<FontIcon iconLiteral="fa-remove"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</AnchorPane>
|
||||||
|
|
||||||
|
<VBox alignment="CENTER" spacing="20"
|
||||||
|
AnchorPane.bottomAnchor="30" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0">
|
||||||
|
|
||||||
|
<Label fx:id="toptip" style="-fx-background-radius: 10;-fx-background-color: -color-button-bg-hover;"
|
||||||
|
styleClass="admin-toptip, button, flat, danger" text="%admin.pwd.toptip"/>
|
||||||
|
|
||||||
|
<Pane style="-fx-background-color: transparent"/>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER" styleClass="admin-field">
|
||||||
|
<Label fx:id="usernameLabel" text="%admin.pwd.user-field"/>
|
||||||
|
<InputGroup fx:id="userField" styleClass="admin-field-value">
|
||||||
|
<TextField fx:id="usernameField" text="admin" editable="false"/>
|
||||||
|
<Button fx:id="copyUsername" onAction="#copyUsername">
|
||||||
|
<graphic>
|
||||||
|
<FontIcon iconLiteral="fa-copy"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER" styleClass="admin-field">
|
||||||
|
<Label fx:id="passwordLabel" text="%admin.pwd.pwd-field"/>
|
||||||
|
<InputGroup styleClass="admin-field-value">
|
||||||
|
<PasswordField fx:id="passwordField" editable="false"/>
|
||||||
|
<Button fx:id="refreshPassword" onAction="#savePassword" visible="false" managed="false">
|
||||||
|
<graphic>
|
||||||
|
<FontIcon iconLiteral="fa-refresh"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="savePassword" onAction="#savePassword" visible="false" managed="false">
|
||||||
|
<graphic>
|
||||||
|
<FontIcon iconLiteral="fa-save"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
<Button fx:id="copyPassword" onAction="#copyPassword">
|
||||||
|
<graphic>
|
||||||
|
<FontIcon iconLiteral="fa-copy"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
</AnchorPane>
|
||||||
51
src/main/resources/fxml/main-view.fxml
Normal file
51
src/main/resources/fxml/main-view.fxml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<VBox fx:id="mainView" prefHeight="700" prefWidth="720" stylesheets="@../css/main-view.css"
|
||||||
|
xmlns="http://javafx.com/javafx/11.0.14-internal" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
alignment="TOP_CENTER"
|
||||||
|
fx:controller="cn.octopusyan.alistgui.controller.MainController">
|
||||||
|
<padding>
|
||||||
|
<Insets left="10.0" right="10.0" top="10.0"/>
|
||||||
|
</padding>
|
||||||
|
<HBox alignment="TOP_CENTER" prefWidth="Infinity">
|
||||||
|
<Label fx:id="homeLabel" alignment="CENTER" text="AList GUI"/>
|
||||||
|
<Button fx:id="statusLabel" styleClass="danger" alignment="TOP_CENTER" text="%main.status.label-stop">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets left="-10.0" top="-5"/>
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="TOP_CENTER" prefWidth="Infinity" spacing="25.0">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||||
|
</padding>
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" top="10.0"/>
|
||||||
|
</VBox.margin>
|
||||||
|
<Button fx:id="startButton" onAction="#start" styleClass="control-menu, success"
|
||||||
|
text="%main.control.start"/>
|
||||||
|
<Button fx:id="passwordButton" onAction="#adminPassword" styleClass="control-menu, success"
|
||||||
|
text="%main.control.password"/>
|
||||||
|
<Button fx:id="restartButton" onAction="#restart" styleClass="control-menu, success"
|
||||||
|
text="%main.control.restart"/>
|
||||||
|
<MenuButton fx:id="moreButton" styleClass="button-outlined, no-arrow" text="%main.control.more">
|
||||||
|
<items>
|
||||||
|
<MenuItem fx:id="browserButton" onAction="#openInBrowser" disable="true" text="%main.more.browser"/>
|
||||||
|
<MenuItem fx:id="configButton" onAction="#openConfig" text="%main.more.open-config"/>
|
||||||
|
<MenuItem fx:id="logButton" onAction="#openLogFolder" text="%main.more.open-log"/>
|
||||||
|
</items>
|
||||||
|
</MenuButton>
|
||||||
|
</HBox>
|
||||||
|
<ScrollPane fx:id="logAreaSp" fitToWidth="true" prefHeight="499.0" prefWidth="Infinity"
|
||||||
|
styleClass="logArea" VBox.vgrow="ALWAYS">
|
||||||
|
|
||||||
|
<VBox fx:id="logArea" spacing="10">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets bottom="10.0" top="10.0"/>
|
||||||
|
</VBox.margin>
|
||||||
|
</VBox>
|
||||||
|
</ScrollPane>
|
||||||
|
</VBox>
|
||||||
@ -1,90 +1,61 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.*?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import org.kordamp.ikonli.javafx.*?>
|
<?import org.kordamp.ikonli.javafx.*?>
|
||||||
<VBox fx:id="rootPane" styleClass="rootPane" alignment="TOP_CENTER" prefHeight="620.0" prefWidth="700.0" spacing="10.0"
|
<StackPane fx:id="rootPane" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
|
||||||
stylesheets="@../css/root-view.css" xmlns="http://javafx.com/javafx/11.0.14-internal"
|
prefHeight="720.0" prefWidth="770.0"
|
||||||
xmlns:fx="http://javafx.com/fxml/1" fx:controller="cn.octopusyan.alistgui.controller.MainController">
|
styleClass="root-pane" stylesheets="@../css/root-view.css"
|
||||||
|
fx:controller="cn.octopusyan.alistgui.controller.RootController">
|
||||||
|
|
||||||
|
<VBox prefHeight="720.0" prefWidth="770.0" spacing="10.0">
|
||||||
|
|
||||||
<HBox fx:id="windowHeader" alignment="CENTER_RIGHT" prefWidth="Infinity" spacing="10.0">
|
<HBox fx:id="windowHeader" alignment="CENTER_RIGHT" prefWidth="Infinity" spacing="10.0">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||||
</padding>
|
</padding>
|
||||||
<Button fx:id="alwaysOnTopIcon" styleClass="iconButton"/>
|
<FontIcon fx:id="alwaysOnTopIcon" styleClass="icon-button"/>
|
||||||
<Button fx:id="minimizeIcon" styleClass="iconButton"/>
|
<FontIcon fx:id="minimizeIcon" styleClass="icon-button"/>
|
||||||
<Button fx:id="closeIcon" styleClass="iconButton"/>
|
<FontIcon fx:id="closeIcon" styleClass="icon-button"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<TabPane fx:id="tabPane" prefWidth="Infinity" VBox.vgrow="ALWAYS" tabClosingPolicy="UNAVAILABLE">
|
<TabPane fx:id="tabPane" prefWidth="Infinity" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0"/>
|
<Insets left="20.0" right="20.0"/>
|
||||||
</padding>
|
</padding>
|
||||||
<Tab text="主页">
|
<Tab fx:id="mainTab" text="%root.tab.main">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontIcon iconLiteral="cil-library" iconColor="white"/>
|
<FontIcon iconColor="white" iconLiteral="fa-th-large"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
<VBox>
|
<!-- 引入主页 -->
|
||||||
<padding>
|
<fx:include fx:id="mainController" source="main-view.fxml" prefWidth="Infinity" prefHeight="-Infinity"/>
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
|
||||||
</padding>
|
|
||||||
<HBox styleClass="mainViewHeader" prefWidth="Infinity" alignment="TOP_CENTER">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
|
||||||
</padding>
|
|
||||||
<Label fx:id="homeLabel" text="AList GUI" alignment="CENTER"/>
|
|
||||||
<Label fx:id="statusLabel" text="运行中" alignment="TOP_CENTER">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="-10.0" top="-5"/>
|
|
||||||
</HBox.margin>
|
|
||||||
</Label>
|
|
||||||
</HBox>
|
|
||||||
<HBox prefWidth="Infinity" alignment="TOP_CENTER" spacing="25.0">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
|
||||||
</padding>
|
|
||||||
<VBox.margin>
|
|
||||||
<Insets bottom="10.0" top="10.0"/>
|
|
||||||
</VBox.margin>
|
|
||||||
<Button fx:id="startButton" styleClass="controlMenu" text="开始"/>
|
|
||||||
<Button fx:id="passwordButton" styleClass="controlMenu" text="密码"/>
|
|
||||||
<Button fx:id="restartButton" styleClass="controlMenu" text="重启"/>
|
|
||||||
<Button fx:id="moreButton" styleClass="controlMenu" text="更多"/>
|
|
||||||
</HBox>
|
|
||||||
<TextArea fx:id="logArea" editable="false" wrapText="true" prefWidth="Infinity" VBox.vgrow="ALWAYS"
|
|
||||||
text="123d1a32s1d3as21d3a2s1d3a2s1d3a2s1d3a2s1d3a2s1d3a2s1d32aasda3s21da32s1d32a1sd">
|
|
||||||
<VBox.margin>
|
|
||||||
<Insets bottom="10.0" top="10.0"/>
|
|
||||||
</VBox.margin>
|
|
||||||
</TextArea>
|
|
||||||
<HBox prefWidth="Infinity" alignment="CENTER" spacing="25.0">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="10.0" top="30.0"/>
|
|
||||||
</padding>
|
|
||||||
<Button fx:id="docmentLabel" text="文档" textAlignment="CENTER">
|
|
||||||
<graphic>
|
|
||||||
<FontIcon iconLiteral="cib-readme"/>
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
<Button fx:id="gethubLabel" text="Github" textAlignment="CENTER">
|
|
||||||
<graphic>
|
|
||||||
<FontIcon iconLiteral="cib-github"/>
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
<Button fx:id="otherLabel" text="赞助" textAlignment="CENTER">
|
|
||||||
<graphic>
|
|
||||||
<FontIcon iconLiteral="cib-buy-me-a-coffee"/>
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
</HBox>
|
|
||||||
</VBox>
|
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab fx:id="setupTab" text="设置">
|
<Tab fx:id="setupTab" text="%root.tab.setup">
|
||||||
<graphic>
|
<graphic>
|
||||||
<FontIcon iconLiteral="cil-settings" iconColor="white"/>
|
<FontIcon iconColor="white" iconLiteral="fa-cog"/>
|
||||||
</graphic>
|
</graphic>
|
||||||
|
<!-- 引入设置页 -->
|
||||||
|
<fx:include fx:id="setupController" source="setup-view.fxml" prefWidth="Infinity"
|
||||||
|
prefHeight="-Infinity"/>
|
||||||
|
</Tab>
|
||||||
|
<Tab fx:id="aboutTab" text="%root.tab.about">
|
||||||
|
<graphic>
|
||||||
|
<FontIcon iconColor="white" iconLiteral="fa-info-circle"/>
|
||||||
|
</graphic>
|
||||||
|
<!-- 引入关于页 -->
|
||||||
|
<fx:include fx:id="aboutController" source="about-view.fxml" prefWidth="Infinity"
|
||||||
|
prefHeight="-Infinity"/>
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</VBox>
|
<HBox fx:id="windowFooter" alignment="CENTER" prefWidth="Infinity" spacing="25.0">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="30.0"/>
|
||||||
|
</padding>
|
||||||
|
<Button fx:id="document" onAction="#openDocument" styleClass="success, flat" text="%root.foot.doc"/>
|
||||||
|
<Button fx:id="github" onAction="#openGithub" styleClass="accent, flat" text="%root.foot.github"/>
|
||||||
|
<Button fx:id="sponsor" styleClass="danger, flat" text="%root.foot.sponsor"
|
||||||
|
visible="false" managed="false"/>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</StackPane>
|
||||||
|
|||||||
46
src/main/resources/fxml/setup-view.fxml
Normal file
46
src/main/resources/fxml/setup-view.fxml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<VBox fx:id="setupView" stylesheets="@../css/setup-view.css"
|
||||||
|
prefHeight="700" prefWidth="720" spacing="20"
|
||||||
|
xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
|
||||||
|
fx:controller="cn.octopusyan.alistgui.controller.SetupController">
|
||||||
|
<padding>
|
||||||
|
<Insets left="10.0" right="10.0" top="20.0"/>
|
||||||
|
</padding>
|
||||||
|
<CheckBox fx:id="autoStartCheckBox" text="%setup.auto-start.label"/>
|
||||||
|
<CheckBox fx:id="silentStartupCheckBox" text="%setup.silent-startup.label"/>
|
||||||
|
<CheckBox fx:id="closeToTrayCheckBox" text="%setup.close-to-tray.label"/>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="%setup.theme"/>
|
||||||
|
<ComboBox fx:id="themeComboBox"/>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10">
|
||||||
|
<Label text="%setup.language"/>
|
||||||
|
<ComboBox fx:id="languageComboBox"/>
|
||||||
|
</HBox>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="20">
|
||||||
|
<Label styleClass="proxy-label" text="%setup.proxy"/>
|
||||||
|
<ComboBox fx:id="proxySetupComboBox"/>
|
||||||
|
<Button fx:id="proxyCheck" onAction="#proxyTest" text="%setup.proxy.test"/>
|
||||||
|
</HBox>
|
||||||
|
<GridPane fx:id="proxySetupPane" vgap="10" hgap="10">
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" percentWidth="10"/>
|
||||||
|
<ColumnConstraints hgrow="SOMETIMES" percentWidth="40"/>
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
<RowConstraints vgrow="SOMETIMES"/>
|
||||||
|
</rowConstraints>
|
||||||
|
<padding>
|
||||||
|
<Insets left="30"/>
|
||||||
|
</padding>
|
||||||
|
<Label fx:id="hostLabel" text="%setup.proxy.host"/>
|
||||||
|
<TextField fx:id="proxyHost" promptText="127.0.0.1" GridPane.columnIndex="1"/>
|
||||||
|
<Label fx:id="portLabel" text="%setup.proxy.port" GridPane.rowIndex="1"/>
|
||||||
|
<TextField fx:id="proxyPort" promptText="8080" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||||
|
</GridPane>
|
||||||
|
</VBox>
|
||||||
54
src/main/resources/language/language.properties
Normal file
54
src/main/resources/language/language.properties
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
label.cancel=\u53D6\u6D88
|
||||||
|
root.tab.main=\u4E3B\u9875
|
||||||
|
root.tab.setup=\u8BBE\u7F6E
|
||||||
|
root.tab.about=\u5173\u4E8E
|
||||||
|
root.foot.doc=\u6587\u6863
|
||||||
|
root.foot.github=GitHub
|
||||||
|
root.foot.sponsor=\u8D5E\u52A9 AList
|
||||||
|
main.control.start=\u5F00\u59CB
|
||||||
|
main.control.stop=\u505C\u6B62
|
||||||
|
main.control.password=\u5BC6\u7801
|
||||||
|
main.control.restart=\u91CD\u542F
|
||||||
|
main.control.more=\u66F4\u591A
|
||||||
|
main.status.label-running=\u8FD0\u884C\u4E2D
|
||||||
|
main.status.label-stop=\u5DF2\u505C\u6B62
|
||||||
|
main.more.browser=\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00
|
||||||
|
main.more.open-config=\u6253\u5F00\u914D\u7F6E\u6587\u4EF6
|
||||||
|
main.more.open-log=\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6\u5939
|
||||||
|
alist.status.start.running=AList \u6B63\u5728\u8FD0\u884C\u3002\u3002\u3002
|
||||||
|
alist.status.start=\u6B63\u5728\u542F\u52A8 AList
|
||||||
|
alist.status.stop=\u6B63\u5728\u505C\u6B62AList
|
||||||
|
alist.status.stop.stopped=AList \u5DF2\u505C\u6B62
|
||||||
|
setup.proxy=HTTP\u4EE3\u7406
|
||||||
|
setup.auto-start.label=\u5F00\u673A\u81EA\u542F
|
||||||
|
setup.silent-startup.label=\u9759\u9ED8\u542F\u52A8
|
||||||
|
setup.language=\u8BED\u8A00
|
||||||
|
proxy.setup.label.no_proxy=\u4E0D\u4EE3\u7406
|
||||||
|
proxy.setup.label.system=\u7CFB\u7EDF\u4EE3\u7406
|
||||||
|
proxy.setup.label.manual=\u624B\u52A8\u8BBE\u7F6E
|
||||||
|
setup.proxy.host=\u4E3B\u673A
|
||||||
|
setup.proxy.port=\u7AEF\u53E3
|
||||||
|
setup.proxy.test=\u6D4B\u8BD5
|
||||||
|
proxy.test.header=\u8BF7\u8F93\u5165\u60A8\u8981\u68C0\u67E5\u7684\u4EFB\u4F55URL\uFF1A
|
||||||
|
proxy.test.title=\u68C0\u67E5\u4EE3\u7406\u8BBE\u7F6E
|
||||||
|
proxy.test.result.success=\u8FDE\u63A5\u6210\u529F
|
||||||
|
proxy.test.result.failed=\u8FDE\u63A5\u95EE\u9898:
|
||||||
|
about.alist.version=AList \u7248\u672C
|
||||||
|
about.app.version=GUI \u7248\u672C
|
||||||
|
about.alist.update=\u68C0\u67E5 AList \u7248\u672C
|
||||||
|
about.app.update=\u68C0\u67E5 GUI \u7248\u672C
|
||||||
|
setup.theme=\u4E3B\u9898
|
||||||
|
update.current=\u5F53\u524D\u7248\u672C
|
||||||
|
update.remote=\u6700\u65B0\u7248\u672C
|
||||||
|
update.upgrade.not=\u5DF2\u662F\u6700\u65B0\u7248\u672C
|
||||||
|
update.upgrade.new=\u68C0\u67E5\u5230\u65B0\u7248\u672C
|
||||||
|
msg.alist.download.notfile=\u6CA1\u68C0\u6D4B\u5230AList\u6587\u4EF6\uFF0C\u662F\u5426\u73B0\u5728\u4E0B\u8F7D\uFF1F
|
||||||
|
msg.alist.pwd.copy=\u590D\u5236\u6210\u529F
|
||||||
|
admin.pwd.title=\u7BA1\u7406\u5458\u5BC6\u7801
|
||||||
|
admin.pwd.toptip=\u65B0\u5BC6\u7801\u53EA\u4F1A\u663E\u793A\u4E00\u6B21
|
||||||
|
admin.pwd.user-field=\u7528\u6237\uFF1A
|
||||||
|
admin.pwd.pwd-field=\u5BC6\u7801\uFF1A
|
||||||
|
setup.close-to-tray.label=\u5173\u95ED\u65F6\u6700\u5C0F\u5316\u5230\u6258\u76D8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
53
src/main/resources/language/language_en.properties
Normal file
53
src/main/resources/language/language_en.properties
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
label.cancel=Cancel
|
||||||
|
root.tab.main=Home
|
||||||
|
root.tab.setup=Setup
|
||||||
|
root.tab.about=About
|
||||||
|
root.foot.doc=Document
|
||||||
|
root.foot.github=GitHub
|
||||||
|
root.foot.sponsor=Sponsor AList
|
||||||
|
main.control.start=Start
|
||||||
|
main.control.stop=Stop
|
||||||
|
main.control.password=Password
|
||||||
|
main.control.restart=Restart
|
||||||
|
main.control.more=More
|
||||||
|
main.status.label-running=Running
|
||||||
|
main.status.label-stop=Stoped
|
||||||
|
main.more.browser=Open in browser
|
||||||
|
main.more.open-config=Open configuration file
|
||||||
|
main.more.open-log=Open the log folder
|
||||||
|
alist.status.start=Starting AList
|
||||||
|
alist.status.start.running=AList is running...
|
||||||
|
alist.status.stop=Stopping AList
|
||||||
|
alist.status.stop.stopped=AList has stopped
|
||||||
|
setup.proxy=HTTP PROXY
|
||||||
|
setup.auto-start.label=Auto start with PC
|
||||||
|
setup.silent-startup.label=Silent startup
|
||||||
|
setup.language=language
|
||||||
|
proxy.setup.label.no_proxy=No Proxy
|
||||||
|
proxy.setup.label.system=System Proxy
|
||||||
|
proxy.setup.label.manual=Manual Config
|
||||||
|
setup.proxy.host=Host
|
||||||
|
setup.proxy.port=Port
|
||||||
|
setup.proxy.test=Check connection
|
||||||
|
proxy.test.header=Enter any URL to check connection to:
|
||||||
|
proxy.test.title=Check Proxy Settings
|
||||||
|
proxy.test.result.success=Connection successful
|
||||||
|
proxy.test.result.failed=Problem with connection:
|
||||||
|
about.alist.version=AList Version
|
||||||
|
about.app.version=GUI Version
|
||||||
|
about.alist.update=Check AList Version
|
||||||
|
about.app.update=Check GUI Version
|
||||||
|
setup.theme=Theme
|
||||||
|
update.current=Current Version
|
||||||
|
update.remote=Latest Version
|
||||||
|
update.upgrade.not=It is already the latest version
|
||||||
|
update.upgrade.new=Detected a new version
|
||||||
|
msg.alist.download.notfile=AList file not detected, download now?
|
||||||
|
msg.alist.pwd.copy=Copy successful
|
||||||
|
admin.pwd.title=Admin Password
|
||||||
|
admin.pwd.toptip=The new password will only be displayed once
|
||||||
|
admin.pwd.user-field=User:
|
||||||
|
admin.pwd.pwd-field=Password :
|
||||||
|
setup.close-to-tray.label=Minimize to tray when closed
|
||||||
|
|
||||||
|
|
||||||
53
src/main/resources/language/language_zh_CN.properties
Normal file
53
src/main/resources/language/language_zh_CN.properties
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
label.cancel=\u53D6\u6D88
|
||||||
|
root.tab.main=\u4E3B\u9875
|
||||||
|
root.tab.setup=\u8BBE\u7F6E
|
||||||
|
root.tab.about=\u5173\u4E8E
|
||||||
|
root.foot.doc=\u6587\u6863
|
||||||
|
root.foot.github=GitHub
|
||||||
|
root.foot.sponsor=\u8D5E\u52A9 AList
|
||||||
|
main.control.start=\u5F00\u59CB
|
||||||
|
main.control.stop=\u505C\u6B62
|
||||||
|
main.control.password=\u5BC6\u7801
|
||||||
|
main.control.restart=\u91CD\u542F
|
||||||
|
main.control.more=\u66F4\u591A
|
||||||
|
main.status.label-running=\u8FD0\u884C\u4E2D
|
||||||
|
main.status.label-stop=\u5DF2\u505C\u6B62
|
||||||
|
main.more.browser=\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00
|
||||||
|
main.more.open-config=\u6253\u5F00\u914D\u7F6E\u6587\u4EF6
|
||||||
|
main.more.open-log=\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6\u5939
|
||||||
|
alist.status.stop.stopped=AList \u5DF2\u505C\u6B62
|
||||||
|
alist.status.start=\u6B63\u5728\u542F\u52A8 AList
|
||||||
|
alist.status.stop=\u6B63\u5728\u505C\u6B62AList
|
||||||
|
alist.status.start.running=AList \u6B63\u5728\u8FD0\u884C\u3002\u3002\u3002
|
||||||
|
setup.proxy=HTTP\u4EE3\u7406
|
||||||
|
setup.auto-start.label=\u5F00\u673A\u81EA\u542F
|
||||||
|
setup.silent-startup.label=\u9759\u9ED8\u542F\u52A8
|
||||||
|
setup.language=\u8BED\u8A00
|
||||||
|
proxy.setup.label.no_proxy=\u4E0D\u4EE3\u7406
|
||||||
|
proxy.setup.label.system=\u7CFB\u7EDF\u4EE3\u7406
|
||||||
|
proxy.setup.label.manual=\u624B\u52A8\u8BBE\u7F6E
|
||||||
|
setup.proxy.host=\u4E3B\u673A
|
||||||
|
setup.proxy.port=\u7AEF\u53E3
|
||||||
|
setup.proxy.test=\u6D4B\u8BD5
|
||||||
|
proxy.test.header=\u8BF7\u8F93\u5165\u60A8\u8981\u68C0\u67E5\u7684\u4EFB\u4F55URL\uFF1A
|
||||||
|
proxy.test.title=\u68C0\u67E5\u4EE3\u7406\u8BBE\u7F6E
|
||||||
|
proxy.test.result.success=\u8FDE\u63A5\u6210\u529F
|
||||||
|
proxy.test.result.failed=\u8FDE\u63A5\u95EE\u9898:
|
||||||
|
about.alist.version=AList \u7248\u672C
|
||||||
|
about.app.version=GUI \u7248\u672C
|
||||||
|
about.alist.update=\u68C0\u67E5 AList \u7248\u672C
|
||||||
|
about.app.update=\u68C0\u67E5 GUI \u7248\u672C
|
||||||
|
setup.theme=\u4E3B\u9898
|
||||||
|
update.current=\u5F53\u524D\u7248\u672C
|
||||||
|
update.remote=\u6700\u65B0\u7248\u672C
|
||||||
|
update.upgrade.not=\u5DF2\u662F\u6700\u65B0\u7248\u672C
|
||||||
|
update.upgrade.new=\u68C0\u67E5\u5230\u65B0\u7248\u672C
|
||||||
|
msg.alist.download.notfile=\u6CA1\u68C0\u6D4B\u5230AList\u6587\u4EF6\uFF0C\u662F\u5426\u73B0\u5728\u4E0B\u8F7D\uFF1F
|
||||||
|
msg.alist.pwd.copy=\u590D\u5236\u6210\u529F
|
||||||
|
admin.pwd.title=\u7BA1\u7406\u5458\u5BC6\u7801
|
||||||
|
admin.pwd.toptip=\u65B0\u5BC6\u7801\u53EA\u4F1A\u663E\u793A\u4E00\u6B21
|
||||||
|
admin.pwd.user-field=\u7528\u6237\uFF1A
|
||||||
|
admin.pwd.pwd-field=\u5BC6\u7801\uFF1A
|
||||||
|
setup.close-to-tray.label=\u5173\u95ED\u65F6\u6700\u5C0F\u5316\u5230\u6258\u76D8
|
||||||
|
|
||||||
|
|
||||||
@ -5,7 +5,8 @@
|
|||||||
<property name="CHARSET" value="utf-8"/>
|
<property name="CHARSET" value="utf-8"/>
|
||||||
<property name="logback.app" value="alist-gui"/>
|
<property name="logback.app" value="alist-gui"/>
|
||||||
<!-- 彩色日志格式 -->
|
<!-- 彩色日志格式 -->
|
||||||
<property name="CONSOLE_LOG_PATTERN" value="%highlight(%d{HH:mm:ss.SSS}) ${logback.app} %boldYellow([%thread]) %highlight(%-5level) %cyan(%logger{36}) - %mdc{client} [%X{trace_id}] %msg%n"/>
|
<property name="CONSOLE_LOG_PATTERN"
|
||||||
|
value="%highlight(%d{YYYY:MM:dd HH:mm:ss.SSS}) ${logback.app} %boldYellow([%thread]) %highlight(%-5level) %cyan(%logger{36}) - %mdc{client} [%X{trace_id}] %msg%n"/>
|
||||||
|
|
||||||
<!--输出到控制台 ConsoleAppender-->
|
<!--输出到控制台 ConsoleAppender-->
|
||||||
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|||||||
Reference in New Issue
Block a user