Compare commits

..

16 Commits

Author SHA1 Message Date
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
24 changed files with 289 additions and 94 deletions

View File

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

2
.gitignore vendored
View File

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

View File

@ -18,6 +18,40 @@
### TODO ### TODO
### 截图
<details>
<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>
<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>
<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>
<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. 克隆代码 1. 克隆代码

24
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>cn.octopusyan</groupId> <groupId>cn.octopusyan</groupId>
<artifactId>alist-gui</artifactId> <artifactId>alist-gui</artifactId>
<version>1.0.0</version> <version>1.0.2</version>
<name>alist-gui</name> <name>alist-gui</name>
<organization> <organization>
@ -238,6 +238,7 @@
<mainClass>cn.octopusyan.alistgui/${exec.mainClass}</mainClass> <mainClass>cn.octopusyan.alistgui/${exec.mainClass}</mainClass>
<options> <options>
<option>--enable-preview</option> <option>--enable-preview</option>
<!-- <option>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005</option>-->
</options> </options>
</configuration> </configuration>
</execution> </execution>
@ -258,18 +259,18 @@
<vmArgs> <vmArgs>
<arg>--enable-preview</arg> <arg>--enable-preview</arg>
<arg>-Xmx100m</arg> <arg>-Xmx100m</arg>
<!-- <arg>-Djava.awt.headless=false</arg>-->
</vmArgs> </vmArgs>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
<id>bundling-for-windows</id> <id>windows</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>package</goal> <goal>package</goal>
</goals> </goals>
<configuration> <configuration>
<platform>windows</platform> <platform>windows</platform>
<zipballName>${project.name}-windows</zipballName>
<createZipball>true</createZipball> <createZipball>true</createZipball>
<winConfig> <winConfig>
<headerType>gui</headerType> <headerType>gui</headerType>
@ -277,6 +278,23 @@
</winConfig> </winConfig>
</configuration> </configuration>
</execution> </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>
</configuration>
</execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>

View File

@ -18,11 +18,12 @@ import lombok.Getter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.*;
import java.net.InetSocketAddress; import java.net.*;
import java.net.ProxySelector;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
public class Application extends javafx.application.Application { public class Application extends javafx.application.Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class); private static final Logger logger = LoggerFactory.getLogger(Application.class);
@ -32,6 +33,10 @@ public class Application extends javafx.application.Application {
@Override @Override
public void init() { public void init() {
logger.info("application init ..."); logger.info("application init ...");
// 单例模式检查
makeSingle();
// 初始化客户端配置 // 初始化客户端配置
ConfigManager.load(); ConfigManager.load();
@ -83,6 +88,7 @@ public class Application extends javafx.application.Application {
primaryStage.setTitle(String.format("%s v%s", Constants.APP_TITLE, Constants.APP_VERSION)); primaryStage.setTitle(String.format("%s v%s", Constants.APP_TITLE, Constants.APP_VERSION));
Scene scene = Context.initScene(); Scene scene = Context.initScene();
primaryStage.setScene(scene); primaryStage.setScene(scene);
primaryStage.show(); primaryStage.show();
// 静默启动 // 静默启动
@ -113,4 +119,84 @@ public class Application extends javafx.application.Application {
Platform.exit(); Platform.exit();
System.exit(0); 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) {
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

@ -4,7 +4,7 @@ import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.config.I18n; import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.util.FxmlUtil; 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.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.control.Labeled; import javafx.scene.control.Labeled;
@ -83,7 +83,7 @@ public abstract class BaseController<VM extends BaseViewModel> implements Initia
// 全局窗口拖拽 // 全局窗口拖拽
if (dragWindow() && getRootPanel() != null) { if (dragWindow() && getRootPanel() != null) {
// 窗口拖拽 // 窗口拖拽
WindowsUtil.bindDragged(getRootPanel()); ViewUtil.bindDragged(getRootPanel());
} }
// 国际化绑定 // 国际化绑定

View File

@ -20,8 +20,7 @@ public class Constants {
public static final String TMP_DIR_PATH = System.getProperty("java.io.tmpdir") + APP_NAME; 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 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."\{DATA_DIR_PATH}\{File.separator}gui.yaml";
public static final String GUI_CONFIG_PATH = STR."\{CONFIG_DIR_PATH}\{File.separator}gui.yaml";
public static final String BAK_FILE_PATH = STR."\{Constants.TMP_DIR_PATH}\{File.separator}bak"; public static final String BAK_FILE_PATH = STR."\{Constants.TMP_DIR_PATH}\{File.separator}bak";
public static final String REG_AUTO_RUN = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"; public static final String REG_AUTO_RUN = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";

View File

@ -3,7 +3,6 @@ package cn.octopusyan.alistgui.controller;
import cn.octopusyan.alistgui.base.BaseController; import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.I18n; import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.view.alert.AlertUtil;
import cn.octopusyan.alistgui.viewModel.AboutViewModule; import cn.octopusyan.alistgui.viewModel.AboutViewModule;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@ -60,8 +59,7 @@ public class AboutController extends BaseController<AboutViewModule> {
} }
public void checkGuiUpdate() { public void checkGuiUpdate() {
// TODO 检查 gui 版本 viewModel.checkUpdate(ConfigManager.gui());
AlertUtil.info("待开发。。。").show();
} }
} }

View File

@ -6,7 +6,7 @@ import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.config.I18n; import cn.octopusyan.alistgui.config.I18n;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.SystemTrayManager; 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 cn.octopusyan.alistgui.viewModel.RootViewModel;
import com.gluonhq.emoji.EmojiData; import com.gluonhq.emoji.EmojiData;
import com.gluonhq.emoji.util.EmojiImageUtils; import com.gluonhq.emoji.util.EmojiImageUtils;
@ -114,12 +114,13 @@ public class RootController extends BaseController<RootViewModel> {
@Override @Override
public void initViewAction() { public void initViewAction() {
closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> { closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> {
Platform.setImplicitExit(!ConfigManager.closeToTray());
if (ConfigManager.closeToTray()) { if (ConfigManager.closeToTray()) {
SystemTrayManager.show(); SystemTrayManager.show();
} else { } else {
SystemTrayManager.hide(); SystemTrayManager.hide();
Platform.exit();
} }
Platform.setImplicitExit(!ConfigManager.closeToTray());
getWindow().close(); getWindow().close();
}); });
minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> getWindow().setIconified(true)); minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, _ -> getWindow().setIconified(true));
@ -129,7 +130,7 @@ public class RootController extends BaseController<RootViewModel> {
getWindow().setAlwaysOnTop(newVal); getWindow().setAlwaysOnTop(newVal);
}); });
WindowsUtil.bindDragged(windowHeader); ViewUtil.bindDragged(windowHeader);
viewModel.currentViewIndexProperty().bind(tabPane.getSelectionModel().selectedIndexProperty()); viewModel.currentViewIndexProperty().bind(tabPane.getSelectionModel().selectedIndexProperty());
} }
@ -143,6 +144,11 @@ public class RootController extends BaseController<RootViewModel> {
Context.openUrl("https://github.com/alist-org/alist"); 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) { public void showModal(Node node, boolean persistent) {
modalPane.show(node); modalPane.show(node);
modalPane.setPersistent(persistent); modalPane.setPersistent(persistent);

View File

@ -37,8 +37,14 @@ public class SetupController extends BaseController<SetupViewModel> implements I
public CheckBox silentStartupCheckBox; public CheckBox silentStartupCheckBox;
@I18n(key = "setup.close-to-tray.label") @I18n(key = "setup.close-to-tray.label")
public CheckBox closeToTrayCheckBox; public CheckBox closeToTrayCheckBox;
public ComboBox<Locale> languageComboBox; @I18n(key = "setup.theme")
public Label themeLabel;
public ComboBox<Theme> themeComboBox; public ComboBox<Theme> themeComboBox;
@I18n(key = "setup.language")
public Label languageLabel;
public ComboBox<Locale> languageComboBox;
@I18n(key = "setup.proxy")
public Label proxySetupLabel;
public ComboBox<ProxySetup> proxySetupComboBox; public ComboBox<ProxySetup> proxySetupComboBox;
public Pane proxySetupPane; public Pane proxySetupPane;
@I18n(key = "setup.proxy.test") @I18n(key = "setup.proxy.test")

View File

@ -205,7 +205,7 @@ public class AListManager {
} }
var task = new CheckUpdateTask(ConfigManager.aList()); var task = new CheckUpdateTask(ConfigManager.aList());
task.onListen(new TaskListener.UpgradeUpgradeListener(task) { task.onListen(new TaskListener.UpgradeListener(task) {
@Override @Override
public void onChecked(boolean hasUpgrade, String version) { public void onChecked(boolean hasUpgrade, String version) {
Platform.runLater(() -> showDownload(version)); Platform.runLater(() -> showDownload(version));

View File

@ -84,10 +84,10 @@ public class ConfigManager {
File parent = FileUtil.getParent(src, 1); File parent = FileUtil.getParent(src, 1);
if (!parent.exists()) { if (!parent.exists()) {
boolean wasSuccessful = parent.mkdirs(); boolean wasSuccessful = parent.mkdirs();
objectMapper.writeValue(src, clazz.getDeclaredConstructor().newInstance());
if (!wasSuccessful) if (!wasSuccessful)
logger.error("{} 创建失败", src.getAbsolutePath()); logger.error("{} 创建失败", src.getAbsolutePath());
} }
objectMapper.writeValue(src, clazz.getDeclaredConstructor().newInstance());
} }
public static void save() { public static void save() {
@ -278,6 +278,10 @@ public class ConfigManager {
} }
public static String guiVersion() { public static String guiVersion() {
// 覆盖配置文件读取的版本号
if (!Constants.APP_VERSION.equals(gui().getVersion())) {
guiVersion(Constants.APP_VERSION);
}
return gui().getVersion(); return gui().getVersion();
} }

View File

@ -3,7 +3,7 @@ package cn.octopusyan.alistgui.manager;
import cn.octopusyan.alistgui.Application; import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.config.Constants; import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context; 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 cn.octopusyan.alistgui.view.PopupMenu;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.StringBinding; import javafx.beans.binding.StringBinding;
@ -48,7 +48,7 @@ public class SystemTrayManager {
public static void icon(String path) { public static void icon(String path) {
if (trayIcon == null) return; if (trayIcon == null) return;
icon(WindowsUtil.class.getResource(path)); icon(ViewUtil.class.getResource(path));
} }
public static void icon(URL url) { public static void icon(URL url) {
@ -100,7 +100,7 @@ public class SystemTrayManager {
if (trayIcon != null) return; if (trayIcon != null) return;
// 系统托盘图标 // 系统托盘图标
URL resource = WindowsUtil.class.getResource(STR."/assets/logo\{running ? "" : "-disabled"}.png"); URL resource = ViewUtil.class.getResource(STR."/assets/logo\{running ? "" : "-disabled"}.png");
Image image = Toolkit.getDefaultToolkit().getImage(resource); Image image = Toolkit.getDefaultToolkit().getImage(resource);
trayIcon = new TrayIcon(image); trayIcon = new TrayIcon(image);
@ -187,6 +187,6 @@ public class SystemTrayManager {
} }
private static Stage stage() { 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 autoStart = false;
private Boolean silentStartup = false; private Boolean silentStartup = false;
private Boolean closeToTray = true; private Boolean closeToTray = false;
@JsonProperty("proxy") @JsonProperty("proxy")
private ProxyInfo proxyInfo; private ProxyInfo proxyInfo;
@JsonProperty("proxy.testUrl") @JsonProperty("proxy.testUrl")

View File

@ -10,18 +10,12 @@ import lombok.Data;
@Data @Data
public class Gui implements UpgradeApp { public class Gui implements UpgradeApp {
@JsonIgnore @JsonIgnore
private final String owner = "alist-org"; private final String owner = "octopusYan";
@JsonIgnore @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"); 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; package cn.octopusyan.alistgui.task;
import cn.octopusyan.alistgui.base.BaseTask; import cn.octopusyan.alistgui.base.BaseTask;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.manager.http.HttpUtil; import cn.octopusyan.alistgui.manager.http.HttpUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -13,10 +12,12 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class DownloadTask extends BaseTask { public class DownloadTask extends BaseTask {
private final String downloadUrl; private final String downloadUrl;
private final String savePath;
public DownloadTask(String downloadUrl) { public DownloadTask(String downloadUrl, String savePath) {
super(STR."Download \{downloadUrl}"); super(STR."Download \{downloadUrl}");
this.downloadUrl = downloadUrl; this.downloadUrl = downloadUrl;
this.savePath = savePath;
} }
public void onListen(DownloadListener listener) { public void onListen(DownloadListener listener) {
@ -27,7 +28,7 @@ public class DownloadTask extends BaseTask {
protected void task() throws Exception { protected void task() throws Exception {
HttpUtil.getInstance().download( HttpUtil.getInstance().download(
downloadUrl, downloadUrl,
Constants.BIN_DIR_PATH, savePath,
listener instanceof DownloadListener ? ((DownloadListener) listener)::onProgress : null 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 static abstract class UpgradeListener extends TaskListener implements CheckUpdateTask.UpgradeListener {
public UpgradeUpgradeListener(BaseTask task) { public UpgradeListener(BaseTask task) {
super(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.StrUtil;
import cn.hutool.core.util.ZipUtil; import cn.hutool.core.util.ZipUtil;
import cn.octopusyan.alistgui.config.Constants; 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.manager.ConsoleLog;
import cn.octopusyan.alistgui.model.upgrade.AList; 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.model.upgrade.UpgradeApp;
import cn.octopusyan.alistgui.task.DownloadTask; import cn.octopusyan.alistgui.task.DownloadTask;
import cn.octopusyan.alistgui.task.listener.TaskListener; import cn.octopusyan.alistgui.task.listener.TaskListener;
@ -24,7 +27,6 @@ import java.util.zip.ZipFile;
@Slf4j @Slf4j
public class DownloadUtil { public class DownloadUtil {
/** /**
* 下载文件 * 下载文件
* *
@ -32,12 +34,19 @@ public class DownloadUtil {
* @param version 下载版本 * @param version 下载版本
*/ */
public static DownloadTask startDownload(UpgradeApp app, String version, Runnable runnable) { 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) { task.onListen(new TaskListener.DownloadListener(task) {
@Override @Override
public void onRunning() { public void onRunning() {
// 不展示进度条 // 不展示进度条
RootController root = (RootController) Context.getControllers().get(RootController.class.getSimpleName());
root.showTab(0);
} }
@Override @Override
@ -64,6 +73,11 @@ public class DownloadUtil {
path = StrUtil.replace(path, "*", "_"); path = StrUtil.replace(path, "*", "_");
} }
// 打包后文件都在alist-gui文件夹下解压时去掉
if (app instanceof Gui) {
path = path.replaceFirst(Constants.APP_NAME, "");
}
final File outItemFile = FileUtil.file(parentPath, path); final File outItemFile = FileUtil.file(parentPath, path);
if (zipEntry.isDirectory()) { if (zipEntry.isDirectory()) {
// 目录 // 目录

View File

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

View File

@ -2,7 +2,7 @@ package cn.octopusyan.alistgui.view;
import atlantafx.base.controls.CaptionMenuItem; import atlantafx.base.controls.CaptionMenuItem;
import cn.octopusyan.alistgui.config.Constants; import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.util.WindowsUtil; import cn.octopusyan.alistgui.util.ViewUtil;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.StringBinding; import javafx.beans.binding.StringBinding;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@ -115,8 +115,8 @@ public class PopupMenu {
root.hide(); root.hide();
root.show(utilityStage, root.show(utilityStage,
event.getX() / WindowsUtil.scaleX, event.getX() / ViewUtil.scaleX,
event.getY() / WindowsUtil.scaleY event.getY() / ViewUtil.scaleY
); );
// 获取焦点 (失去焦点隐藏自身) // 获取焦点 (失去焦点隐藏自身)
root.requestFocus(); root.requestFocus();

View File

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

View File

@ -1,14 +1,19 @@
package cn.octopusyan.alistgui.viewModel; package cn.octopusyan.alistgui.viewModel;
import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.base.BaseViewModel; import cn.octopusyan.alistgui.base.BaseViewModel;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.AListManager;
import cn.octopusyan.alistgui.manager.ConfigManager; import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.ConsoleLog; import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.model.upgrade.AList; 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.model.upgrade.UpgradeApp;
import cn.octopusyan.alistgui.task.CheckUpdateTask; import cn.octopusyan.alistgui.task.CheckUpdateTask;
import cn.octopusyan.alistgui.task.listener.TaskListener; import cn.octopusyan.alistgui.task.listener.TaskListener;
import cn.octopusyan.alistgui.util.DownloadUtil; 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.AlertUtil;
import cn.octopusyan.alistgui.view.alert.builder.AlertBuilder; import cn.octopusyan.alistgui.view.alert.builder.AlertBuilder;
import javafx.application.Platform; import javafx.application.Platform;
@ -61,52 +66,22 @@ public class AboutViewModule extends BaseViewModel {
* 检查更新 * 检查更新
*/ */
public void checkUpdate(UpgradeApp app) { public void checkUpdate(UpgradeApp app) {
// 检查任务 // 检查任务
startUpgrade(app, () -> { startUpgrade(app, () -> onChecked(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();
});
});
} }
/**
* 开始检查更新
*
* @param app 更新的应用
* @param runnable 检查后执行的任务
*/
private void startUpgrade(UpgradeApp app, Runnable runnable) { private void startUpgrade(UpgradeApp app, Runnable runnable) {
// 检查更新的任务 // 检查更新的任务
var task = new CheckUpdateTask(app); var task = new CheckUpdateTask(app);
// 任务监听 // 任务监听
task.onListen(new TaskListener.UpgradeUpgradeListener(task) { task.onListen(new TaskListener.UpgradeListener(task) {
@Override @Override
protected void onSucceed() { protected void onSucceed() {
@ -135,4 +110,62 @@ public class AboutViewModule extends BaseViewModel {
// 执行任务 // 执行任务
task.execute(); 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, () -> {
// 下载完成后,解压并删除文件
DownloadUtil.unzip(app);
// 解压后
Platform.runLater(() -> {
switch (app) {
case AList _ -> {
// 设置应用版本
aListVersion.setValue(aListNewVersion.getValue());
AListManager.restart();
}
case Gui _ -> {
log.info(STR."guiNewVersion => \{guiNewVersion.get()}");
// 重启
Platform.setImplicitExit(true);
Application.getPrimaryStage().close();
ProcessesUtil.init(Constants.DATA_DIR_PATH).exec(STR."\{Constants.APP_NAME}.exe");
}
default -> throw new IllegalStateException(STR."Unexpected value: \{app}");
}
});
}).execute();
});
}
} }

View File

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

View File

@ -14,15 +14,15 @@
<CheckBox fx:id="silentStartupCheckBox" text="%setup.silent-startup.label"/> <CheckBox fx:id="silentStartupCheckBox" text="%setup.silent-startup.label"/>
<CheckBox fx:id="closeToTrayCheckBox" text="%setup.close-to-tray.label"/> <CheckBox fx:id="closeToTrayCheckBox" text="%setup.close-to-tray.label"/>
<HBox alignment="CENTER_LEFT" spacing="10"> <HBox alignment="CENTER_LEFT" spacing="10">
<Label text="%setup.theme"/> <Label fx:id="themeLabel" text="%setup.theme"/>
<ComboBox fx:id="themeComboBox"/> <ComboBox fx:id="themeComboBox"/>
</HBox> </HBox>
<HBox alignment="CENTER_LEFT" spacing="10"> <HBox alignment="CENTER_LEFT" spacing="10">
<Label text="%setup.language"/> <Label fx:id="languageLabel" text="%setup.language"/>
<ComboBox fx:id="languageComboBox"/> <ComboBox fx:id="languageComboBox"/>
</HBox> </HBox>
<HBox alignment="CENTER_LEFT" spacing="20"> <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"/> <ComboBox fx:id="proxySetupComboBox"/>
<Button fx:id="proxyCheck" onAction="#proxyTest" text="%setup.proxy.test"/> <Button fx:id="proxyCheck" onAction="#proxyTest" text="%setup.proxy.test"/>
</HBox> </HBox>