diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d3608f2..16a7cd5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -121,10 +121,12 @@ jobs:
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')
diff --git a/.gitignore b/.gitignore
index 5a49883..a2a9479 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,8 +6,8 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
-/config/
/bin/
+gui.yaml
### IntelliJ IDEA ###
.idea/
diff --git a/pom.xml b/pom.xml
index b2b1ae5..8ef597e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
cn.octopusyan
alist-gui
- 1.0.1
+ 1.0.2
alist-gui
@@ -238,6 +238,7 @@
cn.octopusyan.alistgui/${exec.mainClass}
+
diff --git a/src/main/java/cn/octopusyan/alistgui/Application.java b/src/main/java/cn/octopusyan/alistgui/Application.java
index 26169e5..79c106f 100644
--- a/src/main/java/cn/octopusyan/alistgui/Application.java
+++ b/src/main/java/cn/octopusyan/alistgui/Application.java
@@ -18,11 +18,12 @@ 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.io.*;
+import java.net.*;
import java.net.http.HttpClient;
import java.util.Objects;
+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);
@@ -32,6 +33,10 @@ public class Application extends javafx.application.Application {
@Override
public void init() {
logger.info("application init ...");
+
+ // 单例模式检查
+ makeSingle();
+
// 初始化客户端配置
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));
Scene scene = Context.initScene();
primaryStage.setScene(scene);
+
primaryStage.show();
// 静默启动
@@ -113,4 +119,84 @@ public class Application extends javafx.application.Application {
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 JavaFX单实例运行应用程序
+ */
+ 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();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/cn/octopusyan/alistgui/config/Constants.java b/src/main/java/cn/octopusyan/alistgui/config/Constants.java
index 59db2bd..7127f24 100644
--- a/src/main/java/cn/octopusyan/alistgui/config/Constants.java
+++ b/src/main/java/cn/octopusyan/alistgui/config/Constants.java
@@ -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 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";
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
index 99c85e5..efd9e83 100644
--- a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
+++ b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
@@ -114,12 +114,13 @@ public class RootController extends BaseController {
@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));
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
index 5e2f529..1f12631 100644
--- a/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
+++ b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
@@ -37,8 +37,14 @@ public class SetupController extends BaseController implements I
public CheckBox silentStartupCheckBox;
@I18n(key = "setup.close-to-tray.label")
public CheckBox closeToTrayCheckBox;
- public ComboBox languageComboBox;
+ @I18n(key = "setup.theme")
+ public Label themeLabel;
public ComboBox themeComboBox;
+ @I18n(key = "setup.language")
+ public Label languageLabel;
+ public ComboBox languageComboBox;
+ @I18n(key = "setup.proxy")
+ public Label proxySetupLabel;
public ComboBox proxySetupComboBox;
public Pane proxySetupPane;
@I18n(key = "setup.proxy.test")
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java b/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
index d3e1992..80b5c36 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
+++ b/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
@@ -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();
}
diff --git a/src/main/resources/fxml/setup-view.fxml b/src/main/resources/fxml/setup-view.fxml
index eb3888e..4ea0c23 100644
--- a/src/main/resources/fxml/setup-view.fxml
+++ b/src/main/resources/fxml/setup-view.fxml
@@ -14,15 +14,15 @@
-
+
-
+
-
+