Compare commits

...

33 Commits

Author SHA1 Message Date
a8db7cbf88 Release v1.0.3-beta
Release v1.0.3-beta
2024-11-17 17:06:02 +08:00
6a4e425f8a docs: version v1.0.3 2024-11-17 16:22:46 +08:00
09f515025c fix: 修复在AList运行时更新会报错“该文件正在被使用”的问题 2024-11-17 16:21:34 +08:00
96274f6952 chore: README.md 2024-11-11 22:53:27 +08:00
b468b9774d chore: 手动代理修改配置 2024-11-11 00:01:32 +08:00
17d21fdc03 chore: 日志输出处理 2024-11-05 22:32:21 +08:00
894439de6f docs: 打包配置 2024-10-30 09:43:33 +08:00
9bbc3f488f pref: 优化GUI更新功能;项目分为gui和upgrade两个模块 2024-10-30 00:18:20 +08:00
4dc13bb8f1 Release v1.0.2
Release v1.0.2
2024-10-27 15:29:57 +08:00
ea922e6a3f docs: version 1.0.2 2024-10-27 15:24:54 +08:00
3dc60a89e1 chore: 修改GUI配置文件位置 2024-10-19 22:03:38 +08:00
30f05240f9 fix: 补充设置界面语言切换 2024-10-19 17:33:05 +08:00
115e672f26 ci: 非release版本不上传工件 2024-10-19 17:14:41 +08:00
4988cdc31e feat: GUI单例模式 2024-09-26 22:18:55 +08:00
4e1af001c2 fix: 修复更新后版本号显示为配置文件内旧版本号的错误 2024-09-26 21:14:48 +08:00
ef7f4461c2 Release v1.0.1
fix Release v1.0.1
2024-09-26 20:47:52 +08:00
6670dc7210 ci: Modify the name of the file to be uploaded 2024-09-26 20:43:56 +08:00
98c4ec2cd8 Release v1.0.1
Release v1.0.1
2024-09-26 20:36:15 +08:00
7e0ec8b598 docs: version 1.0.1 2024-09-26 20:31:06 +08:00
47b001cdf8 Release v1.0.1
Release v1.0.1
2024-09-23 02:58:34 +08:00
b9e842a80c docs: README.md 添加界面截图
Some checks are pending
Auto Alpha Tag / auto-tag (x64, x64) (push) Waiting to run
Release / meta (push) Waiting to run
Release / windows (x64, x64) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions
2024-09-21 20:11:48 +08:00
899f286682 style: 默认关闭托盘 2024-09-21 19:39:18 +08:00
229662bd15 pref: 开始下载后跳转主界面查看下载进度 2024-09-21 19:37:51 +08:00
6294fdbf42 chore: 修改类名 WindowsUtil -> ViewUtil 2024-09-21 19:36:21 +08:00
fbdf6b3ba7 feat: 添加GUI检查更新功能,添加nojre打包方式 2024-09-21 19:34:32 +08:00
828a2fdb62 fix: 修复代理类型下拉框无法跟随语言切换的问题
Some checks are pending
Auto Alpha Tag / auto-tag (x64, x64) (push) Waiting to run
Release / meta (push) Waiting to run
Release / windows (x64, x64) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions
2024-09-21 12:46:21 +08:00
4702396bbc fix: 密码弹窗 2024-09-21 10:13:43 +08:00
0660674f7f pref: I18n 自动绑定
Some checks failed
Auto Alpha Tag / auto-tag (x64, x64) (push) Has been cancelled
Release / meta (push) Has been cancelled
Release / windows (x64, x64) (push) Has been cancelled
Release / release (push) Has been cancelled
2024-09-20 22:06:59 +08:00
1b3a4f5569 fix: 切换语言时theme重复bind导致报错的问题
Some checks failed
Auto Alpha Tag / auto-tag (x64, x64) (push) Has been cancelled
Release / meta (push) Has been cancelled
Release / windows (x64, x64) (push) Has been cancelled
Release / release (push) Has been cancelled
2024-09-19 12:34:52 +08:00
be7e17665f chore: clean up 2024-09-19 12:33:24 +08:00
654b13e150 fix: 托盘初始化时与主界面状态同步 2024-09-19 12:25:22 +08:00
329c484f4c ci: fix Auto Alpha Tag
Some checks are pending
Auto Alpha Tag / auto-tag (x64, x64) (push) Waiting to run
Release / meta (push) Waiting to run
Release / windows (x64, x64) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions
2024-09-19 01:13:45 +08:00
ec585ea29d Release v1.0.0
Some checks are pending
Auto Alpha Tag / auto-tag (x64, x64) (push) Waiting to run
Release / meta (push) Waiting to run
Release / windows (x64, x64) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions
Release v1.0.0
2024-09-18 20:48:50 +08:00
100 changed files with 1851 additions and 754 deletions

View File

@ -4,6 +4,11 @@ on:
push:
branches:
- "dev"
paths:
- ".github/workflows/*.yml"
- "src/**"
- "pom.xml"
- "!**/*.md"
pull_request:
branches:
- "dev"

View File

@ -118,13 +118,15 @@ jobs:
- name: Build with Maven
run: |
mvn clean package -f pom.xml
mkdir zipball && cp target/*-windows.zip zipball
mkdir zipball && cp target/*.zip zipball
- name: Upload AListGUI to Github
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v4
with:
name: AListGUI-windows
path: zipball
retention-days: 5
release:
if: startsWith(github.ref, 'refs/tags/v')

3
.gitignore vendored
View File

@ -6,8 +6,9 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
/config/
/bin/
gui.yaml
upgrade.exe
### IntelliJ IDEA ###
.idea/

View File

@ -18,6 +18,40 @@
### TODO
### 截图
<details open>
<summary> 主界面 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/909ac6ad-0021-47d7-a75c-7fb6505e8c15">
<img alt="main" src="https://github.com/user-attachments/assets/4984f7fb-acaa-4dbc-a322-8b6b89557cbf">
</picture>
</details>
<details open>
<summary> 管理员信息 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/840dca69-e67d-4083-88f8-8e67c3e47141">
<img alt="main" src="https://github.com/user-attachments/assets/a93d5967-65b5-4185-8bfb-77e55d811532">
</picture>
</details>
<details open>
<summary> 设置 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/8fc8c489-b9cd-4e34-ad32-4899ccc275e9">
<img alt="main" src="https://github.com/user-attachments/assets/f4cc78df-0718-4bac-9985-3761611f8f57">
</picture>
</details>
<details open>
<summary> 关于 </summary>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/dbef2d66-4ca4-4e89-8292-dbdce3566f93">
<img alt="about" src="https://github.com/user-attachments/assets/0e474a5d-78f3-4475-a8a9-fca15c3ed515">
</picture>
</details>
#### 本地运行
1. 克隆代码

298
gui/pom.xml Normal file
View File

@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.octopusyan</groupId>
<artifactId>alist-gui</artifactId>
<version>${gui.version}</version>
</parent>
<artifactId>gui</artifactId>
<version>${gui.version}</version>
<name>alist-gui</name>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<parent.version>${gui.version}</parent.version>
<exec.mainClass>cn.octopusyan.alistgui.AppLauncher</exec.mainClass>
<cssSrcPath>${project.basedir}/src/main/resources/css</cssSrcPath>
<cssTargetPath>${project.basedir}/target/classes/css</cssTargetPath>
</properties>
<dependencies>
<!-- javafx -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
</dependency>
<!-- https://mkpaz.github.io/atlantafx/ -->
<dependency>
<groupId>io.github.mkpaz</groupId>
<artifactId>atlantafx-base</artifactId>
</dependency>
<!-- slf4j -->
<!-- https://slf4j.org/manual.html -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<!-- common -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>
<!-- https://kordamp.org/ikonli/ -->
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-javafx</artifactId>
</dependency>
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-fontawesome-pack</artifactId>
</dependency>
<dependency>
<groupId>com.gluonhq</groupId>
<artifactId>emoji</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>css/*.scss</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>--enable-preview</compilerArgs>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<!-- https://github.com/HebiRobotics/sass-cli-maven-plugin -->
<plugin>
<groupId>us.hebi.sass</groupId>
<artifactId>sass-cli-maven-plugin</artifactId>
<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>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<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>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<stripDebug>true</stripDebug>
<compress>2</compress>
<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>
<!-- <option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005</option>-->
</options>
</configuration>
</execution>
</executions>
</plugin>
<!-- https://github.com/fvarrui/JavaPackager -->
<plugin>
<groupId>io.github.fvarrui</groupId>
<artifactId>javapackager</artifactId>
<configuration>
<mainClass>${exec.mainClass}</mainClass>
<bundleJre>true</bundleJre>
<generateInstaller>false</generateInstaller>
<copyDependencies>true</copyDependencies>
<assetsDir>${project.basedir}/src/main/resources/assets</assetsDir>
<vmArgs>
<arg>--enable-preview</arg>
<arg>-Xmx100m</arg>
</vmArgs>
</configuration>
<executions>
<execution>
<id>windows</id>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<platform>windows</platform>
<zipballName>${project.name}-windows</zipballName>
<createZipball>true</createZipball>
<winConfig>
<headerType>gui</headerType>
<generateMsi>false</generateMsi>
</winConfig>
<additionalResources>
<additionalResource>${project.basedir}/src/main/resources/static/upgrade.exe
</additionalResource>
</additionalResources>
</configuration>
</execution>
<execution>
<id>windows-nojre</id>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<zipballName>${project.name}-windows-nojre</zipballName>
<platform>windows</platform>
<createZipball>true</createZipball>
<bundleJre>false</bundleJre>
<winConfig>
<headerType>gui</headerType>
<generateMsi>false</generateMsi>
</winConfig>
<additionalResources>
<additionalResource>${project.basedir}/src/main/resources/static/upgrade.exe
</additionalResource>
</additionalResources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>exe</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<!--打成jar包后复制到的路径-->
<outputDirectory>../target</outputDirectory>
<resources>
<resource>
<!--项目中需要复制的文件路径-->
<directory>${project.basedir}/target</directory>
<includes>
<include>*.zip</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,221 @@
package cn.octopusyan.alistgui;
import cn.hutool.core.io.FileUtil;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context;
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.HttpUtil;
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
import cn.octopusyan.alistgui.model.upgrade.Gui;
import cn.octopusyan.alistgui.util.ProcessesUtil;
import cn.octopusyan.alistgui.view.alert.AlertUtil;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.*;
import java.net.http.HttpClient;
import java.util.Objects;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
public class Application extends javafx.application.Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
@Getter
private static Stage primaryStage;
@Override
public void init() throws Exception {
logger.info("application init ...");
long delay = 0L;
// 更新重启检查
File upgradeFile = new File(Constants.DATA_DIR_PATH + File.separator + new Gui().getReleaseFile());
// logger.error("{}{}{}", Constants.DATA_DIR_PATH, File.separator, new Gui().getReleaseFile());
if (upgradeFile.exists()) {
logger.error("upgradeFile.exists");
FileUtil.del(upgradeFile);
delay = 1000;
}
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// 单例模式检查
makeSingle();
}
}, delay);
// 初始化客户端配置
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
public void start(Stage primaryStage) throws IOException {
logger.info("application start ...");
Application.primaryStage = primaryStage;
Context.setApplication(this);
// 初始化弹窗工具
AlertUtil.initOwner(primaryStage);
// 全局异常处理
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
// i18n
Context.setLanguage(ConfigManager.language());
// 主题样式
Application.setUserAgentStylesheet(ConfigManager.theme().getUserAgentStylesheet());
// 启动主界面
primaryStage.getIcons().add(new Image(Objects.requireNonNull(this.getClass().getResourceAsStream("/assets/logo.png"))));
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.setTitle(String.format("%s %s", Constants.APP_TITLE, Constants.APP_VERSION));
Scene scene = Context.initScene();
primaryStage.setScene(scene);
primaryStage.show();
// 静默启动
if (ConfigManager.silentStartup()) {
Platform.setImplicitExit(false);
primaryStage.hide();
SystemTrayManager.show();
}
logger.info("application start over ...");
}
private void showErrorDialog(Thread t, Throwable e) {
logger.error("", e);
Platform.runLater(() -> AlertUtil.exception(new Exception(e)).show());
}
@Override
public void stop() {
logger.info("application stop ...");
// 关闭所有命令
ProcessesUtil.destroyAll();
// 保存应用数据
ConfigManager.save();
// 停止所有线程
ThreadPoolManager.getInstance().shutdown();
// 关闭主界面
Platform.exit();
System.exit(0);
}
private static final int SINGLE_INSTANCE_LISTENER_PORT = 9009;
private static final String SINGLE_INSTANCE_FOCUS_MESSAGE = "focus";
private static final String instanceId = UUID.randomUUID().toString();
/**
* 我们在聚焦现有实例之前定义一个暂停
* 因为有时启动实例的命令行或窗口
* 可能会在第二个实例执行完成后重新获得焦点
* 所以我们在聚焦原始窗口之前引入了一个短暂的延迟
* 以便原始窗口可以保留焦点。
*/
private static final int FOCUS_REQUEST_PAUSE_MILLIS = 500;
/**
* 单实例检测
*
* @see <a href='https://www.cnblogs.com/shihaiming/p/13553278.html'>JavaFX单实例运行应用程序</url>
*/
public static void makeSingle() {
CountDownLatch instanceCheckLatch = new CountDownLatch(1);
Thread instanceListener = new Thread(() -> {
try (ServerSocket serverSocket = new ServerSocket(SINGLE_INSTANCE_LISTENER_PORT, 10)) {
instanceCheckLatch.countDown();
while (true) {
logger.debug(STR."====\{instanceId}====");
try (
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()))
) {
String input = in.readLine();
logger.info(STR."Received single instance listener message: \{input}");
if (input.startsWith(SINGLE_INSTANCE_FOCUS_MESSAGE) && primaryStage != null) {
//noinspection BusyWait
Thread.sleep(FOCUS_REQUEST_PAUSE_MILLIS);
Platform.runLater(() -> {
logger.info(STR."To front \{instanceId}");
primaryStage.setIconified(false);
primaryStage.show();
primaryStage.toFront();
});
}
} catch (IOException e) {
logger.error("Single instance listener unable to process focus message from client");
}
}
} catch (java.net.BindException b) {
logger.error("SingleInstanceApp already running");
try (
Socket clientSocket = new Socket(InetAddress.getLocalHost(), SINGLE_INSTANCE_LISTENER_PORT);
PrintWriter out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()))
) {
logger.info("Requesting existing app to focus");
out.println(STR."\{SINGLE_INSTANCE_FOCUS_MESSAGE} requested by \{instanceId}");
} catch (IOException e) {
logger.error("", e);
}
logger.info(STR."Aborting execution for instance \{instanceId}");
Platform.exit();
} catch (Exception e) {
logger.error("", e);
} finally {
instanceCheckLatch.countDown();
}
}, "instance-listener");
instanceListener.setDaemon(true);
instanceListener.start();
try {
instanceCheckLatch.await();
} catch (InterruptedException e) {
//noinspection ResultOfMethodCallIgnored
Thread.interrupted();
}
}
}

View File

@ -2,19 +2,28 @@ package cn.octopusyan.alistgui.base;
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.WindowsUtil;
import cn.octopusyan.alistgui.util.ViewUtil;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Labeled;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
/**
@ -43,6 +52,30 @@ public abstract class BaseController<VM extends BaseViewModel> implements Initia
}
}
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
@ -51,9 +84,12 @@ public abstract class BaseController<VM extends BaseViewModel> implements Initia
// 全局窗口拖拽
if (dragWindow() && getRootPanel() != null) {
// 窗口拖拽
WindowsUtil.bindDragged(getRootPanel());
ViewUtil.bindDragged(getRootPanel());
}
// 国际化绑定
bindI18n();
// 初始化数据
initData();
@ -109,4 +145,16 @@ public abstract class BaseController<VM extends BaseViewModel> implements Initia
* 视图事件
*/
public abstract void initViewAction();
private static List<Field> getAllField(Class<?> class1) {
List<Field> list = new ArrayList<>();
while (class1 != Object.class) {
list.addAll(Arrays.stream(class1.getDeclaredFields()).toList());
//获取父类
class1 = class1.getSuperclass();
}
return list;
}
}

View File

@ -15,13 +15,12 @@ public class Constants {
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 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 GUI_CONFIG_PATH = STR."\{DATA_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";

View File

@ -1,17 +1,12 @@
package cn.octopusyan.alistgui.config;
import atlantafx.base.theme.Theme;
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.controller.*;
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.application.Platform;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
@ -41,7 +36,6 @@ public class Context {
private static final Logger log = LoggerFactory.getLogger(Context.class);
private static Scene scene;
private static final IntegerProperty currentViewIndex = new SimpleIntegerProperty(0);
private static final ObjectProperty<Theme> theme = new SimpleObjectProperty<>(ConfigManager.theme());
/**
* 控制器集合
@ -79,6 +73,7 @@ public class Context {
case MainController main -> main;
case SetupController setup -> setup;
case AboutController about -> about;
case PasswordController passwod -> passwod;
default -> throw new IllegalStateException(STR."Unexpected value: \{type}");
};
} catch (Exception e) {
@ -92,10 +87,6 @@ public class Context {
Context.application = application;
}
public static ObjectProperty<Theme> themeProperty() {
return theme;
}
// 获取当前所选时区属性
public static ObjectProperty<Locale> currentLocaleProperty() {
return currentLocale;
@ -150,12 +141,6 @@ public class Context {
return LANGUAGE_RESOURCE_FACTORY.getResourceBundleProperty();
}
/**
* 初始化 语言
*/
private static void initI18n() {
}
/**
* 有此类所在路径决定相对路径
*
@ -173,17 +158,8 @@ public class Context {
* @return Scene
*/
public static Scene initScene() {
// locale监听; 切换后重新加载界面
currentLocaleProperty().addListener((_, _, locale) -> Platform.runLater(Context::loadScene));
// 加载
loadScene();
return scene;
}
private static void loadScene() {
try {
FXMLLoader loader = FxmlUtil.load("root-view");
loader.setControllerFactory(Context.getControlFactory());
//底层面板
Pane root = loader.load();
Optional.ofNullable(scene).ifPresentOrElse(
@ -198,6 +174,7 @@ public class Context {
} catch (Throwable e) {
log.error("loadScene error", e);
}
return scene;
}
public static int currentViewIndex() {

View 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 "";
}

View File

@ -1,5 +1,6 @@
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;
@ -26,15 +27,6 @@ public class ObservableResourceBundleFactory {
}
public StringBinding getStringBinding(String key) {
return new StringBinding() {
{
bind(resourceBundleProperty);
}
@Override
protected String computeValue() {
return getResourceBundle().getString(key);
}
};
return Bindings.createStringBinding(() -> getResourceBundle().getString(key), resourceBundleProperty);
}
}

View File

@ -2,9 +2,8 @@ package cn.octopusyan.alistgui.controller;
import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.view.alert.AlertUtil;
import cn.octopusyan.alistgui.viewModel.AboutViewModule;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
@ -18,12 +17,18 @@ import org.slf4j.LoggerFactory;
public class AboutController extends BaseController<AboutViewModule> {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@FXML
public VBox aboutView;
@FXML
public Label aListVersion;
public Label aListVersionLabel;
public Label appVersionLabel;
public Button checkAppVersion;
public Button checkAListVersion;
@Override
public VBox getRootPanel() {
return aboutView;
@ -44,15 +49,12 @@ public class AboutController extends BaseController<AboutViewModule> {
aListVersion.textProperty().bindBidirectional(viewModel.aListVersionProperty());
}
@FXML
public void checkAListUpdate() {
viewModel.checkUpdate(ConfigManager.aList());
}
@FXML
public void checkGuiUpdate() {
// TODO 检查 gui 版本
AlertUtil.info("待开发。。。").show();
viewModel.checkUpdate(ConfigManager.gui());
}
}

View File

@ -6,9 +6,11 @@ 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.fxml.FXML;
import javafx.application.Platform;
import javafx.beans.binding.StringBinding;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
@ -25,19 +27,26 @@ import java.io.IOException;
public class MainController extends BaseController<MainViewModel> {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@FXML
public VBox mainView;
@FXML
public VBox logArea;
@FXML
public ScrollPane logAreaSp;
@FXML
public Button statusLabel;
@FXML
public Button startButton;
@FXML
public Button passwordButton;
public Button restartButton;
public MenuButton moreButton;
public MenuItem browserButton;
public MenuItem configButton;
public MenuItem logButton;
private PasswordController controller;
@Override
@ -52,19 +61,18 @@ public class MainController extends BaseController<MainViewModel> {
@Override
public void initViewStyle() {
// 运行状态监听
viewModel.runningProperty().addListener((_, _, running) -> {
resetStatus(running);
browserButton.disableProperty().set(!running);
});
}
@Override
public void initViewAction() {
viewModel.startBtnStyleCssProperty().bindContentBidirectional(startButton.getStyleClass());
viewModel.statusLabelStyleCssProperty().bindContentBidirectional(statusLabel.getStyleClass());
viewModel.startBtnTextProperty().bindBidirectional(startButton.textProperty());
viewModel.statusLabelTextProperty().bindBidirectional(statusLabel.textProperty());
viewModel.browserButtonDisableProperty().bindBidirectional(browserButton.disableProperty());
}
// start button
@FXML
public void start() {
if (AListManager.isRunning()) {
AListManager.stop();
@ -74,7 +82,6 @@ public class MainController extends BaseController<MainViewModel> {
}
// password button
@FXML
public void adminPassword() throws IOException {
if (controller == null) {
FXMLLoader load = FxmlUtil.load("admin-panel");
@ -85,29 +92,43 @@ public class MainController extends BaseController<MainViewModel> {
}
// restart button
@FXML
public void restart() {
AListManager.restart();
}
// more button
@FXML
public void openInBrowser() {
AListManager.openScheme();
}
@FXML
public void openLogFolder() {
AListManager.openLogFolder();
}
@FXML
public void openConfig() {
AListManager.openConfig();
}
private String getText(String key) {
return Context.getLanguageBinding(key).get();
/**
* 根据运行状态改变按钮样式
*
* @param running 运行状态
*/
private void resetStatus(boolean running) {
String removeStyle = running ? "success" : "danger";
String addStyle = running ? "danger" : "success";
StringBinding button = Context.getLanguageBinding(STR."main.control.\{running ? "stop" : "start"}");
StringBinding status = Context.getLanguageBinding(STR."main.status.label-\{running ? "running" : "stop"}");
Platform.runLater(() -> {
startButton.getStyleClass().remove(removeStyle);
startButton.getStyleClass().add(addStyle);
startButton.textProperty().bind(button);
statusLabel.getStyleClass().remove(addStyle);
statusLabel.getStyleClass().add(removeStyle);
statusLabel.textProperty().bind(status);
});
}
}

View File

@ -10,6 +10,7 @@ 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;
@ -23,20 +24,17 @@ import org.apache.commons.lang3.StringUtils;
* @author octopus_yan
*/
public class PasswordController extends BaseController<AdminPanelViewModel> {
@FXML
private AnchorPane adminPanel;
public AnchorPane adminPanel;
@FXML
public Label toptip;
public Label usernameLabel;
public TextField usernameField;
@FXML
public Button copyUsername;
@FXML
public Label passwordLabel;
public PasswordField passwordField;
@FXML
public Button refreshPassword;
@FXML
public Button savePassword;
@FXML
public Button copyPassword;
private RootController root;

View File

@ -5,16 +5,16 @@ import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.SystemTrayManager;
import cn.octopusyan.alistgui.util.WindowsUtil;
import cn.octopusyan.alistgui.util.ViewUtil;
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.fxml.FXML;
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;
@ -31,27 +31,22 @@ import java.util.Locale;
*/
public class RootController extends BaseController<RootViewModel> {
// 布局
@FXML
private StackPane rootPane;
@FXML
private HBox windowHeader;
@FXML
private FontIcon alwaysOnTopIcon;
@FXML
private FontIcon minimizeIcon;
@FXML
private FontIcon closeIcon;
public StackPane rootPane;
public HBox windowHeader;
public FontIcon alwaysOnTopIcon;
public FontIcon minimizeIcon;
public FontIcon closeIcon;
// 界面
@FXML
private TabPane tabPane;
public TabPane tabPane;
public Tab mainTab;
public Tab setupTab;
public Tab aboutTab;
// footer
@FXML
public Button document;
@FXML
public Button github;
@FXML
public Button sponsor;
private final ModalPane modalPane = new ModalPane();
@ -94,6 +89,7 @@ public class RootController extends BaseController<RootViewModel> {
sponsor.setGraphic(juice);
});
// 遮罩
getRootPanel().getChildren().add(modalPane);
modalPane.setId("modalPane");
// reset side and transition to reuse a single modal pane between different examples
@ -111,12 +107,13 @@ public class RootController extends BaseController<RootViewModel> {
@Override
public void initViewAction() {
closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> {
Platform.setImplicitExit(!ConfigManager.closeToTray());
if (ConfigManager.closeToTray()) {
SystemTrayManager.show();
} else {
SystemTrayManager.hide();
Platform.exit();
}
Platform.setImplicitExit(!ConfigManager.closeToTray());
getWindow().close();
});
minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> getWindow().setIconified(true));
@ -126,22 +123,25 @@ public class RootController extends BaseController<RootViewModel> {
getWindow().setAlwaysOnTop(newVal);
});
WindowsUtil.bindDragged(windowHeader);
ViewUtil.bindDragged(windowHeader);
viewModel.currentViewIndexProperty().bind(tabPane.getSelectionModel().selectedIndexProperty());
}
@FXML
public void openDocument() {
String locale = Context.getCurrentLocale().equals(Locale.ENGLISH) ? "" : "zh/";
Context.openUrl(STR."https://alist.nn.ci/\{locale}");
}
@FXML
public void openGithub() {
Context.openUrl("https://github.com/alist-org/alist");
}
public void showTab(int index) {
if (index < 0 || index > 2) return;
tabPane.getSelectionModel().select(index);
}
public void showModal(Node node, boolean persistent) {
modalPane.show(node);
modalPane.setPersistent(persistent);

View File

@ -5,14 +5,12 @@ import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.enums.ProxySetup;
import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.view.ProxySetupCell;
import cn.octopusyan.alistgui.viewModel.SetupViewModel;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.control.*;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
@ -32,26 +30,21 @@ public class SetupController extends BaseController<SetupViewModel> implements I
@FXML
public VBox setupView;
@FXML
public CheckBox autoStartCheckBox;
@FXML
public CheckBox silentStartupCheckBox;
@FXML
public CheckBox closeToTrayCheckBox;
@FXML
public ComboBox<Locale> languageComboBox;
@FXML
public Label themeLabel;
public ComboBox<Theme> themeComboBox;
@FXML
public Label languageLabel;
public ComboBox<Locale> languageComboBox;
public Label proxySetupLabel;
public ComboBox<ProxySetup> proxySetupComboBox;
@FXML
public Pane proxySetupPane;
@FXML
public Button proxyCheck;
@FXML
public TextField proxyHost;
@FXML
public TextField proxyPort;
public Label hostLabel;
public Label portLabel;
@Override
public VBox getRootPanel() {
@ -64,6 +57,9 @@ public class SetupController extends BaseController<SetupViewModel> implements I
themeComboBox.setItems(FXCollections.observableList(ConfigManager.THEME_LIST));
proxySetupComboBox.setItems(FXCollections.observableList(List.of(ProxySetup.values())));
proxySetupComboBox.setCellFactory(_ -> new ProxySetupCell());
proxySetupComboBox.setButtonCell(new ProxySetupCell());
themeComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Theme object) {
@ -82,6 +78,14 @@ public class SetupController extends BaseController<SetupViewModel> implements I
proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
proxySetupPane.setVisible(ProxySetup.MANUAL.equals(newValue));
proxyCheck.setVisible(!ProxySetup.NO_PROXY.equals(newValue));
// proxySetupComboBox.promptTextProperty().bind(
//// Bindings.createStringBinding(
//// () -> Context.getLanguageBinding(STR."proxy.setup.label.\{newValue.getName()}").get(),
//// Context.currentLocaleProperty()
//// )
// Context.getLanguageBinding(STR."proxy.setup.label.\{newValue.getName()}")
// );
});
languageComboBox.getSelectionModel().select(ConfigManager.language());
@ -103,7 +107,6 @@ public class SetupController extends BaseController<SetupViewModel> implements I
viewModel.proxySetupProperty().bind(proxySetupComboBox.getSelectionModel().selectedItemProperty());
}
@FXML
public void proxyTest() {
viewModel.proxyTest();
}

View File

@ -1,6 +1,7 @@
package cn.octopusyan.alistgui.enums;
import cn.octopusyan.alistgui.config.Context;
import javafx.beans.binding.StringBinding;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@ -20,6 +21,10 @@ public enum ProxySetup {
@Override
public String toString() {
return Context.getLanguageBinding("proxy.setup.label." + getName()).getValue();
return getBinding().get();
}
public StringBinding getBinding() {
return Context.getLanguageBinding(STR."proxy.setup.label.\{getName()}");
}
}

View File

@ -143,24 +143,21 @@ public class AListManager {
ConsoleLog.info(getText("alist.status.stop"));
if (!running.get()) {
ConsoleLog.warning(getText("alist.status.stop.stopped"));
return;
}
util.destroy();
}
static ChangeListener<Boolean> restartListener;
static ChangeListener<Boolean> restartListener = (_, _, run) -> {
if (run) return;
running.removeListener(AListManager.restartListener);
start();
};
public static void restart() {
if (!running.get()) {
start();
} else {
stop();
restartListener = (_, _, run) -> {
if (run) return;
running.removeListener(restartListener);
start();
};
running.addListener(restartListener);
}
}
@ -194,7 +191,7 @@ public class AListManager {
//============================={ private }====================================
/**
* TODO 点击开始时检查 aList 执行文件
* 点击开始时检查 aList 执行文件
*/
private static boolean checkAList() {
if (new File(Constants.ALIST_FILE).exists()) return true;
@ -205,7 +202,7 @@ public class AListManager {
}
var task = new CheckUpdateTask(ConfigManager.aList());
task.onListen(new TaskListener.UpgradeUpgradeListener(task) {
task.onListen(new TaskListener.UpgradeListener(task) {
@Override
public void onChecked(boolean hasUpgrade, String version) {
Platform.runLater(() -> showDownload(version));
@ -216,6 +213,11 @@ public class AListManager {
return false;
}
/**
* 开始下载AList
*
* @param version 下载版本号
*/
private static void showDownload(String version) {
String content = STR."""
\{getText("msg.alist.download.notfile")}

View File

@ -84,10 +84,10 @@ public class ConfigManager {
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());
}
objectMapper.writeValue(src, clazz.getDeclaredConstructor().newInstance());
}
public static void save() {
@ -278,6 +278,10 @@ public class ConfigManager {
}
public static String guiVersion() {
// 覆盖配置文件读取的版本号
if (!Constants.APP_VERSION.equals(gui().getVersion())) {
guiVersion(Constants.APP_VERSION);
}
return gui().getVersion();
}

View File

@ -19,9 +19,10 @@ 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.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@ -33,7 +34,7 @@ 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_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})?\\/?$";
@ -88,16 +89,6 @@ public class ConsoleLog {
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);
@ -187,17 +178,35 @@ public class ConsoleLog {
/**
* 控制台输出颜色
*
* @param msg alist 输出消息
* @param msg 输出消息
* @return bbcode 颜色文本
*/
private static String resetConsoleColor(String msg) {
if (!msg.contains("\033[")) return msg;
if (!msg.contains(CONSOLE_COLOR_PREFIX) || !Pattern.matches(CONSOLE_MSG_REX, msg)) 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;
// 多颜色处理
String[] split = Pattern.compile("\\033\\[(\\d;)?(\\d+)m")
.matcher(msg)
.replaceAll(matchResult -> "\n" + matchResult.group())
.replaceFirst("\n", "")
.split("\n");
StringBuilder sb = new StringBuilder();
Pattern pattern = Pattern.compile("\\033\\[(\\d;)?(\\d+)m(.*)");
Matcher matcher;
for (int i = 0; i < split.length; i++) {
matcher = pattern.matcher(split[i]);
if(!matcher.matches()) continue;
if (i % 2 == 0) {
String color = StringUtils.lowerCase(Color.valueOf(Integer.parseInt(matcher.group(2))).getColor());
sb.append(color(color, matcher.group(3)));
} else {
sb.append(matcher.group(3));
}
}
return sb.toString();
}
/**

View File

@ -3,9 +3,10 @@ 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.util.ViewUtil;
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;
@ -47,7 +48,7 @@ public class SystemTrayManager {
public static void icon(String path) {
if (trayIcon == null) return;
icon(WindowsUtil.class.getResource(path));
icon(ViewUtil.class.getResource(path));
}
public static void icon(URL url) {
@ -76,7 +77,7 @@ public class SystemTrayManager {
return;
}
initTrayIcon();
initTrayIcon(AListManager.isRunning());
try {
if (!isShowing())
@ -95,11 +96,12 @@ public class SystemTrayManager {
//========================================={ private }===========================================
private static void initTrayIcon() {
private static void initTrayIcon(boolean running) {
if (trayIcon != null) return;
// 系统托盘图标
Image image = Toolkit.getDefaultToolkit().getImage(WindowsUtil.class.getResource("/assets/logo-disabled.png"));
URL resource = ViewUtil.class.getResource(STR."/assets/logo\{running ? "" : "-disabled"}.png");
Image image = Toolkit.getDefaultToolkit().getImage(resource);
trayIcon = new TrayIcon(image);
// 设置图标尺寸自动适应
@ -129,7 +131,7 @@ public class SystemTrayManager {
if (event.isPopupTrigger()) {
// 弹出菜单
Platform.runLater(() -> {
initPopupMenu();
initPopupMenu(running);
popupMenu.show(event);
});
} else if (event.getButton() == MouseEvent.BUTTON1) {
@ -143,15 +145,19 @@ public class SystemTrayManager {
/**
* 构建托盘菜单
*/
private static void initPopupMenu() {
private static void initPopupMenu(boolean running) {
if (popupMenu != null) return;
MenuItem start = PopupMenu.menuItem(getString("main.control.start"), _ -> AListManager.openScheme());
MenuItem browser = PopupMenu.menuItem(getString("main.more.browser"), _ -> AListManager.openScheme());
browser.setDisable(true);
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.setText(getString(STR."main.control.\{newValue ? "stop" : "start"}"));
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");
@ -168,19 +174,19 @@ public class SystemTrayManager {
AListManager.start();
}
})
.addItem(getString("main.control.restart"), _ -> AListManager.restart())
.addMenu(getString("main.control.more"), browser,
PopupMenu.menuItem(getString("main.more.open-config"), _ -> AListManager.openConfig()),
PopupMenu.menuItem(getString("main.more.open-log"), _ -> AListManager.openLogFolder()))
.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 String getString(String key) {
return Context.getLanguageBinding(key).get();
private static StringBinding getStringBinding(String key) {
return Context.getLanguageBinding(key);
}
private static Stage stage() {
return WindowsUtil.getStage();
return ViewUtil.getStage();
}
}
}

View File

@ -18,7 +18,7 @@ public class GuiConfig {
private Boolean autoStart = false;
private Boolean silentStartup = false;
private Boolean closeToTray = true;
private Boolean closeToTray = false;
@JsonProperty("proxy")
private ProxyInfo proxyInfo;
@JsonProperty("proxy.testUrl")

View File

@ -10,18 +10,12 @@ import lombok.Data;
@Data
public class Gui implements UpgradeApp {
@JsonIgnore
private final String owner = "alist-org";
private final String owner = "octopusYan";
@JsonIgnore
private final String repo = "alist";
private final String repo = "alist-gui";
private String releaseFile = "alist-gui-windows-nojre.zip";
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);
}
}

View File

@ -1,7 +1,6 @@
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;
@ -13,10 +12,12 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DownloadTask extends BaseTask {
private final String downloadUrl;
private final String savePath;
public DownloadTask(String downloadUrl) {
public DownloadTask(String downloadUrl, String savePath) {
super(STR."Download \{downloadUrl}");
this.downloadUrl = downloadUrl;
this.savePath = savePath;
}
public void onListen(DownloadListener listener) {
@ -27,7 +28,7 @@ public class DownloadTask extends BaseTask {
protected void task() throws Exception {
HttpUtil.getInstance().download(
downloadUrl,
Constants.BIN_DIR_PATH,
savePath,
listener instanceof DownloadListener ? ((DownloadListener) listener)::onProgress : null
);
}

View File

@ -89,8 +89,8 @@ public abstract class TaskListener implements BaseTask.Listener {
/**
* 检查更新监听默认实现
*/
public static abstract class UpgradeUpgradeListener extends TaskListener implements CheckUpdateTask.UpgradeListener {
public UpgradeUpgradeListener(BaseTask task) {
public static abstract class UpgradeListener extends TaskListener implements CheckUpdateTask.UpgradeListener {
public UpgradeListener(BaseTask task) {
super(task);
}

View File

@ -5,8 +5,11 @@ 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.config.Context;
import cn.octopusyan.alistgui.controller.RootController;
import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.model.upgrade.AList;
import cn.octopusyan.alistgui.model.upgrade.Gui;
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
import cn.octopusyan.alistgui.task.DownloadTask;
import cn.octopusyan.alistgui.task.listener.TaskListener;
@ -24,7 +27,6 @@ import java.util.zip.ZipFile;
@Slf4j
public class DownloadUtil {
/**
* 下载文件
*
@ -32,12 +34,19 @@ public class DownloadUtil {
* @param version 下载版本
*/
public static DownloadTask startDownload(UpgradeApp app, String version, Runnable runnable) {
var task = new DownloadTask(app.getDownloadUrl(version));
var parentPath = switch (app) {
case AList _ -> Constants.BIN_DIR_PATH;
case Gui _ -> Constants.DATA_DIR_PATH;
default -> throw new IllegalStateException(STR."Unexpected value: \{app}");
};
var task = new DownloadTask(app.getDownloadUrl(version), parentPath);
task.onListen(new TaskListener.DownloadListener(task) {
@Override
public void onRunning() {
// 不展示进度条
RootController root = (RootController) Context.getControllers().get(RootController.class.getSimpleName());
root.showTab(0);
}
@Override
@ -53,6 +62,10 @@ public class DownloadUtil {
}
public static void unzip(UpgradeApp app) {
unzip(app, true);
}
public static void unzip(UpgradeApp app, boolean del) {
String parentPath = app instanceof AList ? Constants.BIN_DIR_PATH : Constants.DATA_DIR_PATH;
File file = new File(parentPath + File.separator + app.getReleaseFile());
@ -64,6 +77,11 @@ public class DownloadUtil {
path = StrUtil.replace(path, "*", "_");
}
// 打包后文件都在alist-gui文件夹下解压时去掉
if (app instanceof Gui) {
path = path.replaceFirst(Constants.APP_NAME, "");
}
final File outItemFile = FileUtil.file(parentPath, path);
if (zipEntry.isDirectory()) {
// 目录
@ -79,6 +97,6 @@ public class DownloadUtil {
});
// 解压完成后删除
FileUtil.del(file);
if (del) FileUtil.del(file);
}
}

View File

@ -25,7 +25,7 @@ public class FxmlUtil {
FxmlUtil.class.getResource(prefix + name + suffix),
bundle,
new JavaFXBuilderFactory(),
null,
Context.getControlFactory(),
StandardCharsets.UTF_8
);
}

View File

@ -53,7 +53,7 @@ public class ProcessesUtil {
commandLine = CommandLine.parse(command);
int execute = 0;
try {
execute = executor.execute(commandLine);
executor.execute(commandLine, new DefaultExecuteResultHandler());
} catch (Exception e) {
log.error("exec", e);
}

View File

@ -1,5 +1,7 @@
package cn.octopusyan.alistgui.util;
import lombok.Getter;
/**
* 注册表编辑
*
@ -43,9 +45,9 @@ public class Registry {
RESTORE,
SAVE,
UNLOAD,
;
}
@Getter
public enum Root {
HKCR("HKEY_CLASSES_ROOT"),
HKCU("HKEY_CURRENT_USER"),
@ -60,9 +62,6 @@ public class Registry {
this.path = path;
}
public String getPath() {
return path;
}
}
public enum DataType {
@ -75,6 +74,5 @@ public class Registry {
REG_LINK,
REG_FULL_RESOURCE_DESCRIPTOR,
REG_EXPAND_SZ,
;
}
}

View File

@ -13,7 +13,7 @@ import java.util.Map;
*
* @author octopus_yan
*/
public class WindowsUtil {
public class ViewUtil {
// 获取系统缩放比
public static final double scaleX = Screen.getPrimary().getOutputScaleX();
public static final double scaleY = Screen.getPrimary().getOutputScaleY();

View File

@ -2,8 +2,9 @@ package cn.octopusyan.alistgui.view;
import atlantafx.base.controls.CaptionMenuItem;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.util.WindowsUtil;
import cn.octopusyan.alistgui.util.ViewUtil;
import javafx.application.Platform;
import javafx.beans.binding.StringBinding;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
@ -47,6 +48,12 @@ public class PopupMenu {
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);
@ -68,6 +75,12 @@ public class PopupMenu {
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);
@ -102,8 +115,8 @@ public class PopupMenu {
root.hide();
root.show(utilityStage,
event.getX() / WindowsUtil.scaleX,
event.getY() / WindowsUtil.scaleY
event.getX() / ViewUtil.scaleX,
event.getY() / ViewUtil.scaleY
);
// 获取焦点 (失去焦点隐藏自身)
root.requestFocus();
@ -114,4 +127,11 @@ public class PopupMenu {
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;
}
}

View File

@ -0,0 +1,23 @@
package cn.octopusyan.alistgui.view;
import cn.octopusyan.alistgui.enums.ProxySetup;
import javafx.scene.control.ListCell;
/**
* ProxySetup I18n Cell
*
* @author octopus_yan
*/
public class ProxySetupCell extends ListCell<ProxySetup> {
@Override
protected void updateItem(ProxySetup item, boolean empty) {
super.updateItem(item, empty);
textProperty().unbind();
if (empty || item == null) {
setText("");
} else {
textProperty().bind(item.getBinding());
}
}
}

View File

@ -2,7 +2,7 @@ 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 cn.octopusyan.alistgui.util.ViewUtil;
import javafx.scene.Node;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
@ -26,9 +26,9 @@ public class DefaultBuilder extends BaseBuilder<DefaultBuilder, Dialog<?>> {
DialogPane dialogPane = dialog.getDialogPane();
dialogPane.getScene().setFill(Color.TRANSPARENT);
WindowsUtil.bindDragged(dialogPane);
WindowsUtil.bindShadow(dialogPane);
WindowsUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT);
ViewUtil.bindDragged(dialogPane);
ViewUtil.bindShadow(dialogPane);
ViewUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT);
dialogPane.getButtonTypes().add(new ButtonType(
Context.getLanguageBinding("label.cancel").get(),

View File

@ -0,0 +1,180 @@
package cn.octopusyan.alistgui.viewModel;
import cn.octopusyan.alistgui.base.BaseViewModel;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.AListManager;
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.Gui;
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.util.ProcessesUtil;
import cn.octopusyan.alistgui.view.alert.AlertUtil;
import cn.octopusyan.alistgui.view.alert.builder.AlertBuilder;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
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, () -> onChecked(app));
}
/**
* 开始检查更新
*
* @param app 更新的应用
* @param runnable 检查后执行的任务
*/
private void startUpgrade(UpgradeApp app, Runnable runnable) {
// 检查更新的任务
var task = new CheckUpdateTask(app);
// 任务监听
task.onListen(new TaskListener.UpgradeListener(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();
}
private void onChecked(UpgradeApp 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, () -> {
Platform.runLater(() -> {
switch (app) {
case AList _ -> {
if(AListManager.isRunning()) {
AListManager.stop();
AListManager.runningProperty().addListener(updateListener);
} else {
// 下载完成后,解压并删除文件
DownloadUtil.unzip(app);
}
// 设置应用版本
aListVersion.setValue(aListNewVersion.getValue());
AListManager.restart();
}
case Gui _ -> {
log.info(STR."guiNewVersion => \{guiNewVersion.get()}");
// 启动升级程序
ProcessesUtil.init(Constants.DATA_DIR_PATH).exec("upgrade.exe");
Platform.exit();
}
default -> throw new IllegalStateException(STR."Unexpected value: \{app}");
}
});
}).execute();
});
}
static final ChangeListener<Boolean> updateListener = (_, _, run) -> {
if (!run) {
// 下载完成后,解压并删除文件
DownloadUtil.unzip(ConfigManager.aList());
}
AListManager.runningProperty().removeListener(AboutViewModule.updateListener);
};
}

View File

@ -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;
}
}

View File

@ -36,7 +36,6 @@ public class SetupViewModel extends BaseViewModel {
public SetupViewModel() {
theme.bindBidirectional(Context.themeProperty());
theme.addListener((_, _, newValue) -> ConfigManager.theme(newValue));
silentStartup.addListener((_, _, newValue) -> ConfigManager.silentStartup(newValue));
autoStart.addListener((_, _, newValue) -> {
@ -66,8 +65,14 @@ public class SetupViewModel extends BaseViewModel {
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));
proxyHost.addListener((_, _, newValue) -> {
ConfigManager.proxyHost(newValue);
setProxy();
});
proxyPort.addListener((_, _, newValue) -> {
ConfigManager.proxyPort(newValue);
setProxy();
});
}
public ObjectProperty<Theme> themeProperty() {
@ -124,6 +129,14 @@ public class SetupViewModel extends BaseViewModel {
});
}
private void setProxy() {
ConfigManager.checkProxy((success, _) -> {
if (!success) return;
HttpUtil.getInstance().proxy(ConfigManager.proxySetup(), ConfigManager.getProxyInfo());
});
}
private static ProxyCheckTask getProxyCheckTask(String checkUrl) {
var task = new ProxyCheckTask(checkUrl);
task.onListen(new TaskListener(task) {

View File

@ -24,4 +24,5 @@ module cn.octopusyan.alistgui {
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;
exports cn.octopusyan.alistgui.model.upgrade;
}

View File

@ -1,3 +1,3 @@
app.name=${project.name}
app.title=AList GUI
app.version=${project.version}
app.version=v${project.version}

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 960 B

After

Width:  |  Height:  |  Size: 960 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -22,11 +22,11 @@
</StackPane>
<HBox alignment="CENTER" styleClass="shield">
<Label styleClass="shield-name" text="%about.alist.version"/>
<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 styleClass="shield-name" text="%about.app.version"/>
<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"/>

View File

@ -21,13 +21,13 @@
<VBox alignment="CENTER" spacing="20"
AnchorPane.bottomAnchor="30" AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0">
<Label style="-fx-background-radius: 10;-fx-background-color: -color-button-bg-hover;"
<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 text="%admin.pwd.user-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">
@ -39,7 +39,7 @@
</HBox>
<HBox alignment="CENTER" styleClass="admin-field">
<Label text="%admin.pwd.pwd-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">

View File

@ -34,8 +34,8 @@
<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 onAction="#openConfig" text="%main.more.open-config"/>
<MenuItem onAction="#openLogFolder" text="%main.more.open-log"/>
<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>

View File

@ -24,14 +24,14 @@
<padding>
<Insets left="20.0" right="20.0"/>
</padding>
<Tab text="%root.tab.main">
<Tab fx:id="mainTab" text="%root.tab.main">
<graphic>
<FontIcon iconColor="white" iconLiteral="fa-th-large"/>
</graphic>
<!-- 引入主页 -->
<fx:include fx:id="mainController" source="main-view.fxml" prefWidth="Infinity" prefHeight="-Infinity"/>
</Tab>
<Tab text="%root.tab.setup">
<Tab fx:id="setupTab" text="%root.tab.setup">
<graphic>
<FontIcon iconColor="white" iconLiteral="fa-cog"/>
</graphic>
@ -39,7 +39,7 @@
<fx:include fx:id="setupController" source="setup-view.fxml" prefWidth="Infinity"
prefHeight="-Infinity"/>
</Tab>
<Tab text="%root.tab.about">
<Tab fx:id="aboutTab" text="%root.tab.about">
<graphic>
<FontIcon iconColor="white" iconLiteral="fa-info-circle"/>
</graphic>

View File

@ -14,15 +14,15 @@
<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"/>
<Label fx:id="themeLabel" text="%setup.theme"/>
<ComboBox fx:id="themeComboBox"/>
</HBox>
<HBox alignment="CENTER_LEFT" spacing="10">
<Label text="%setup.language"/>
<Label fx:id="languageLabel" text="%setup.language"/>
<ComboBox fx:id="languageComboBox"/>
</HBox>
<HBox alignment="CENTER_LEFT" spacing="20">
<Label styleClass="proxy-label" text="%setup.proxy"/>
<Label fx:id="proxySetupLabel" styleClass="proxy-label" text="%setup.proxy"/>
<ComboBox fx:id="proxySetupComboBox"/>
<Button fx:id="proxyCheck" onAction="#proxyTest" text="%setup.proxy.test"/>
</HBox>
@ -38,9 +38,9 @@
<padding>
<Insets left="30"/>
</padding>
<Label text="%setup.proxy.host"/>
<Label fx:id="hostLabel" text="%setup.proxy.host"/>
<TextField fx:id="proxyHost" promptText="127.0.0.1" GridPane.columnIndex="1"/>
<Label text="%setup.proxy.port" GridPane.rowIndex="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>

334
pom.xml
View File

@ -6,8 +6,9 @@
<groupId>cn.octopusyan</groupId>
<artifactId>alist-gui</artifactId>
<version>1.0.0</version>
<version>${gui.version}</version>
<name>alist-gui</name>
<packaging>pom</packaging>
<organization>
<name>octopus_yan</name>
@ -17,6 +18,11 @@
<inceptionYear>2024</inceptionYear>
<description>AList GUI</description>
<modules>
<module>upgrade</module>
<module>gui</module>
</modules>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
@ -24,9 +30,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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>
<gui.version>1.0.3</gui.version>
<junit.version>5.10.0</junit.version>
<javafx.version>21.0.4</javafx.version>
@ -42,97 +46,101 @@
<gluonhq-emoji.version>1.0.1</gluonhq-emoji.version>
</properties>
<dependencies>
<!-- javafx -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependencyManagement>
<!-- https://mkpaz.github.io/atlantafx/ -->
<dependency>
<groupId>io.github.mkpaz</groupId>
<artifactId>atlantafx-base</artifactId>
<version>2.0.1</version>
</dependency>
<dependencies>
<!-- slf4j -->
<!-- https://slf4j.org/manual.html -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- javafx -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- common -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${common-lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>${common-exec.version}</version>
</dependency>
<!-- https://mkpaz.github.io/atlantafx/ -->
<dependency>
<groupId>io.github.mkpaz</groupId>
<artifactId>atlantafx-base</artifactId>
<version>2.0.1</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- slf4j -->
<!-- https://slf4j.org/manual.html -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- common -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${common-lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>${common-exec.version}</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- https://kordamp.org/ikonli/ -->
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-javafx</artifactId>
<version>${ikonli.version}</version>
</dependency>
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-fontawesome-pack</artifactId>
<version>${ikonli.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq</groupId>
<artifactId>emoji</artifactId>
<version>${gluonhq-emoji.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- https://kordamp.org/ikonli/ -->
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-javafx</artifactId>
<version>${ikonli.version}</version>
</dependency>
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-fontawesome-pack</artifactId>
<version>${ikonli.version}</version>
</dependency>
<dependency>
<groupId>com.gluonhq</groupId>
<artifactId>emoji</artifactId>
<version>${gluonhq-emoji.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<pluginRepositories>
<pluginRepository>
@ -150,135 +158,35 @@
</pluginRepositories>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>css/*.scss</exclude>
</excludes>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>21</source>
<target>21</target>
<compilerArgs>--enable-preview</compilerArgs>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</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>
</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>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<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>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<stripDebug>true</stripDebug>
<compress>2</compress>
<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>
</execution>
</executions>
</plugin>
<!-- https://github.com/fvarrui/JavaPackager -->
<plugin>
<groupId>io.github.fvarrui</groupId>
<artifactId>javapackager</artifactId>
<version>1.7.7-SNAPSHOT</version>
<configuration>
<mainClass>${exec.mainClass}</mainClass>
<bundleJre>true</bundleJre>
<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>
<executions>
<execution>
<id>bundling-for-windows</id>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<platform>windows</platform>
<createZipball>true</createZipball>
<winConfig>
<headerType>gui</headerType>
<generateMsi>false</generateMsi>
</winConfig>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<!-- https://github.com/fvarrui/JavaPackager -->
<plugin>
<groupId>io.github.fvarrui</groupId>
<artifactId>javapackager</artifactId>
<version>1.7.7-SNAPSHOT</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -1,116 +0,0 @@
package cn.octopusyan.alistgui;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context;
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.HttpUtil;
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
import cn.octopusyan.alistgui.util.ProcessesUtil;
import cn.octopusyan.alistgui.view.alert.AlertUtil;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.util.Objects;
public class Application extends javafx.application.Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
@Getter
private static Stage primaryStage;
@Override
public void init() {
logger.info("application init ...");
// 初始化客户端配置
ConfigManager.load();
// 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
public void start(Stage primaryStage) throws IOException {
logger.info("application start ...");
Application.primaryStage = primaryStage;
Context.setApplication(this);
// 初始化弹窗工具
AlertUtil.initOwner(primaryStage);
// 全局异常处理
Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog);
Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog);
// i18n
Context.setLanguage(ConfigManager.language());
// 主题样式
Application.setUserAgentStylesheet(ConfigManager.theme().getUserAgentStylesheet());
// 启动主界面
primaryStage.getIcons().add(new Image(Objects.requireNonNull(this.getClass().getResourceAsStream("/assets/logo.png"))));
primaryStage.initStyle(StageStyle.TRANSPARENT);
primaryStage.setTitle(String.format("%s v%s", Constants.APP_TITLE, Constants.APP_VERSION));
Scene scene = Context.initScene();
primaryStage.setScene(scene);
primaryStage.show();
// 静默启动
if (ConfigManager.silentStartup()) {
Platform.setImplicitExit(false);
primaryStage.hide();
SystemTrayManager.show();
}
logger.info("application start over ...");
}
private void showErrorDialog(Thread t, Throwable e) {
logger.error("", e);
Platform.runLater(() -> AlertUtil.exception(new Exception(e)).show());
}
@Override
public void stop() {
logger.info("application stop ...");
// 关闭所有命令
ProcessesUtil.destroyAll();
// 保存应用数据
ConfigManager.save();
// 停止所有线程
ThreadPoolManager.getInstance().shutdown();
// 关闭主界面
Platform.exit();
System.exit(0);
}
}

View File

@ -1,138 +0,0 @@
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();
}
}

View File

@ -1,68 +0,0 @@
package cn.octopusyan.alistgui.viewModel;
import cn.octopusyan.alistgui.base.BaseViewModel;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.AListManager;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
/**
* 主界面VM
*
* @author octopus_yan
*/
public class MainViewModel extends BaseViewModel {
private final ListProperty<String> startBtnStyleCss = new SimpleListProperty<>(FXCollections.observableArrayList());
private final StringProperty startBtnText = new SimpleStringProperty();
private final ListProperty<String> statusLabelStyleCss = new SimpleListProperty<>(FXCollections.observableArrayList());
private final StringProperty statusLabelText = new SimpleStringProperty();
private final BooleanProperty browserButtonDisable = new SimpleBooleanProperty();
private final BooleanProperty running = new SimpleBooleanProperty();
public MainViewModel() {
running.addListener((_, _, running) -> {
resetStatus(running);
browserButtonDisable.set(!running);
});
// 先添加监听再绑定解决切换locale后界面状态显示错误的问题
running.bind(AListManager.runningProperty());
}
public ListProperty<String> startBtnStyleCssProperty() {
return startBtnStyleCss;
}
public StringProperty startBtnTextProperty() {
return startBtnText;
}
public ListProperty<String> statusLabelStyleCssProperty() {
return statusLabelStyleCss;
}
public StringProperty statusLabelTextProperty() {
return statusLabelText;
}
public BooleanProperty browserButtonDisableProperty() {
return browserButtonDisable;
}
public void resetStatus(boolean running) {
String removeStyle = running ? "success" : "danger";
String addStyle = running ? "danger" : "success";
String button = Context.getLanguageBinding(STR."main.control.\{running ? "stop" : "start"}").get();
String status = Context.getLanguageBinding(STR."main.status.label-\{running ? "running" : "stop"}").get();
Platform.runLater(() -> {
startBtnStyleCss.remove(removeStyle);
startBtnStyleCss.add(addStyle);
startBtnText.set(button);
statusLabelStyleCss.remove(addStyle);
statusLabelStyleCss.add(removeStyle);
statusLabelText.set(status);
});
}
}

171
upgrade/pom.xml Normal file
View File

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.octopusyan</groupId>
<artifactId>alist-gui</artifactId>
<version>${gui.version}</version>
</parent>
<artifactId>upgrade</artifactId>
<version>${gui.version}</version>
<name>upgrade</name>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<exec.mainClass>cn.octopusyan.upgrade.AppLauncher</exec.mainClass>
</properties>
<dependencies>
<!-- javafx -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
</dependency>
<!-- https://mkpaz.github.io/atlantafx/ -->
<dependency>
<groupId>io.github.mkpaz</groupId>
<artifactId>atlantafx-base</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<configuration>
<stripDebug>true</stripDebug>
<compress>2</compress>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
<launcher>upgrade</launcher>
<jlinkImageName>app</jlinkImageName>
<jlinkZipName>app</jlinkZipName>
<mainClass>cn.octopusyan.upgrade/${exec.mainClass}</mainClass>
</configuration>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<stripDebug>true</stripDebug>
<compress>2</compress>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
<launcher>upgrade</launcher>
<jlinkImageName>app</jlinkImageName>
<jlinkZipName>app</jlinkZipName>
<mainClass>cn.octopusyan.upgrade/${exec.mainClass}</mainClass>
</configuration>
</execution>
</executions>
</plugin>
<!-- https://github.com/fvarrui/JavaPackager -->
<plugin>
<groupId>io.github.fvarrui</groupId>
<artifactId>javapackager</artifactId>
<configuration>
<mainClass>${exec.mainClass}</mainClass>
<bundleJre>true</bundleJre>
<generateInstaller>false</generateInstaller>
<copyDependencies>true</copyDependencies>
<vmArgs>
<arg>-Xmx100m</arg>
</vmArgs>
</configuration>
<executions>
<execution>
<id>windows</id>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<platform>windows</platform>
<createZipball>false</createZipball>
<winConfig>
<headerType>gui</headerType>
<generateMsi>false</generateMsi>
</winConfig>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>UTF-8</encoding>
<!--打成jar包后复制到的路径-->
<outputDirectory>../gui/src/main/resources/static</outputDirectory>
<resources>
<resource>
<!--项目中需要复制的文件路径-->
<directory>./target/upgrade</directory>
<includes>
<include>upgrade.exe</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
<!--可配置多个提取复制路径只需要 “<id>”名字不一样即可-->
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package cn.octopusyan.upgrade;
/**
* 启动类
*
* @author octopus_yan@foxmail.com
*/
public class AppLauncher {
public static void main(String[] args) {
Application.launch(Application.class, args);
}
}

View File

@ -0,0 +1,35 @@
package cn.octopusyan.upgrade;
import atlantafx.base.theme.PrimerLight;
import cn.octopusyan.upgrade.util.FxmlUtil;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.ResourceBundle;
public class Application extends javafx.application.Application {
@Override
public void start(Stage primaryStage) throws IOException {
// 主题样式
Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet());
// 启动主界面
Pane root = FxmlUtil.init("/fxml/hello-view.fxml").load();
Scene scene = new Scene(root, 420, 240);
primaryStage.setTitle(ResourceBundle.getBundle("language/language").getString("title"));
primaryStage.setScene(scene);
primaryStage.show();
}
@Override
public void stop() {
// 关闭主界面
Platform.exit();
System.exit(0);
}
}

View File

@ -0,0 +1,75 @@
package cn.octopusyan.upgrade.alert;
import cn.octopusyan.upgrade.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 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 interface OnChoseListener {
void confirm();
default void cancelOrClose(ButtonType buttonType) {
}
}
public interface OnClickListener {
void onClicked(String result);
}
}

View File

@ -0,0 +1,108 @@
package cn.octopusyan.upgrade.alert.builder;
import cn.octopusyan.upgrade.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);
}
});
}
}

View File

@ -0,0 +1,44 @@
package cn.octopusyan.upgrade.alert.builder;
import javafx.application.Platform;
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() {
Platform.runLater(() -> dialog.showAndWait());
}
public void close() {
if (dialog.isShowing())
dialog.close();
}
}

View File

@ -0,0 +1,19 @@
package cn.octopusyan.upgrade.util;
import javafx.fxml.FXMLLoader;
import javafx.fxml.JavaFXBuilderFactory;
import java.nio.charset.StandardCharsets;
import java.util.ResourceBundle;
public class FxmlUtil {
public static FXMLLoader init(String path) {
return new FXMLLoader(
FxmlUtil.class.getResource(path),
ResourceBundle.getBundle("language/language"),
new JavaFXBuilderFactory(),
null,
StandardCharsets.UTF_8
);
}
}

View File

@ -0,0 +1,83 @@
package cn.octopusyan.upgrade.view;
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.upgrade.alert.AlertUtil;
import javafx.application.Platform;
import javafx.fxml.FXML;
import java.io.*;
import java.nio.file.Paths;
import java.util.zip.ZipFile;
public class HelloController {
public static final String APP_NAME = "alist-gui";
public static final String PARENT_PATH = Paths.get("").toFile().getAbsolutePath();
@FXML
public void onUpdate() {
// 更新检查
File file = new File(PARENT_PATH + File.separator + APP_NAME + "-windows-nojre.zip");
if (!file.exists()) {
AlertUtil.error("The upgrade file does not exist!").show();
return;
}
// 解压
unzip();
// 启动GUI
startGui();
// 退出
onExit();
}
@FXML
public void onExit() {
Platform.exit();
}
private void startGui() {
Runtime runtime = Runtime.getRuntime(); //获取Runtime实例
//执行命令
String[] command = {APP_NAME + ".exe"};
try {
Process process = runtime.exec(command);
} catch (IOException ignored) {
}
}
private void unzip() {
File file = new File(PARENT_PATH + File.separator + APP_NAME + "-windows-nojre.zip");
ZipFile zipFile = ZipUtil.toZipFile(file, CharsetUtil.defaultCharset());
ZipUtil.read(zipFile, zipEntry -> {
String path = zipEntry.getName();
if (FileUtil.isWindows()) {
// Win系统下
path = StrUtil.replace(path, "*", "_");
}
// 打包后文件都在alist-gui文件夹下解压时去掉
path = path.replaceFirst(APP_NAME, "");
final File outItemFile = FileUtil.file(PARENT_PATH, path);
if (zipEntry.isDirectory()) {
// 目录
//noinspection ResultOfMethodCallIgnored
outItemFile.mkdirs();
} else {
InputStream in = ZipUtil.getStream(zipFile, zipEntry);
// 文件
FileUtil.writeFromStream(in, outItemFile, false);
}
});
// 解压完成后删除
FileUtil.del(file);
}
}

View File

@ -0,0 +1,16 @@
module cn.octopusyan.upgrade {
requires javafx.controls;
requires javafx.fxml;
requires atlantafx.base;
requires cn.hutool.core;
requires static lombok;
requires org.apache.commons.lang3;
opens cn.octopusyan.upgrade to javafx.fxml;
exports cn.octopusyan.upgrade;
exports cn.octopusyan.upgrade.util;
opens cn.octopusyan.upgrade.util to javafx.fxml;
exports cn.octopusyan.upgrade.view;
opens cn.octopusyan.upgrade.view to javafx.fxml;
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.HBox?>
<HBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml/1"
xmlns="http://javafx.com/javafx/11.0.14-internal"
fx:controller="cn.octopusyan.upgrade.view.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Button onAction="#onUpdate" styleClass="large" text="%update"/>
<Button onAction="#onExit" styleClass="large" text="%exit"/>
</HBox>

View File

@ -0,0 +1,7 @@
exit=\u9000\u51FA
title=\u66F4\u65B0
update=\u66F4\u65B0

View File

@ -0,0 +1,6 @@
exit=Exit
title=Upgrade App
update=Upgrade

View File

@ -0,0 +1,6 @@
exit=\u9000\u51FA
title=\u66F4\u65B0
update=\u66F4\u65B0