, D extends Dialog<
return (T) this;
}
- public D getDialog() {
- return dialog;
- }
-
public void show() {
- dialog.showAndWait();
+
+ Node dialogPane = dialog.getDialogPane().getContent();
+ if (dialogPane != null && ConfigManager.theme().isDarkMode()) {
+ dialogPane.setStyle(STR."""
+ \{dialogPane.getStyle()}
+ -fx-border-color: rgb(209, 209, 214, 0.5);
+ -fx-border-width: 1;
+ -fx-border-radius: 10;
+ """);
+ }
+
+ Platform.runLater(() -> dialog.showAndWait());
}
public void close() {
diff --git a/src/main/java/cn/octopusyan/alistgui/base/BaseController.java b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java
index f7618cf..c1e48e7 100644
--- a/src/main/java/cn/octopusyan/alistgui/base/BaseController.java
+++ b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java
@@ -2,15 +2,18 @@ package cn.octopusyan.alistgui.base;
import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.config.Context;
-import cn.octopusyan.alistgui.manager.WindowsUtil;
import cn.octopusyan.alistgui.util.FxmlUtil;
+import cn.octopusyan.alistgui.util.WindowsUtil;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
+import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.net.URL;
import java.util.ResourceBundle;
@@ -19,19 +22,34 @@ import java.util.ResourceBundle;
*
* @author octopus_yan@foxmail.com
*/
-public abstract class BaseController implements Initializable {
+public abstract class BaseController implements Initializable {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
+ @Getter
+ protected final VM viewModel;
public BaseController() {
//初始化时保存当前Controller实例
Context.getControllers().put(this.getClass().getSimpleName(), this);
+
+ // view model
+ VM vm = null;
+ Type superclass = getClass().getGenericSuperclass();
+ if (superclass instanceof ParameterizedType type) {
+ Class clazz = (Class) type.getActualTypeArguments()[0];
+ try {
+ vm = clazz.getDeclaredConstructor().newInstance();
+ } catch (Exception e) {
+ logger.error("", e);
+ }
+ }
+ viewModel = vm;
}
@FXML
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// 全局窗口拖拽
- if (dragWindow()) {
+ if (dragWindow() && getRootPanel() != null) {
// 窗口拖拽
WindowsUtil.bindDragged(getRootPanel());
}
@@ -60,7 +78,7 @@ public abstract class BaseController implements Initializable {
*
* @return 根布局对象
*/
- public abstract P getRootPanel();
+ public abstract Pane getRootPanel();
/**
* 获取根布局
diff --git a/src/main/java/cn/octopusyan/alistgui/base/BaseTask.java b/src/main/java/cn/octopusyan/alistgui/base/BaseTask.java
index f37096e..eaf99a9 100644
--- a/src/main/java/cn/octopusyan/alistgui/base/BaseTask.java
+++ b/src/main/java/cn/octopusyan/alistgui/base/BaseTask.java
@@ -1,8 +1,8 @@
package cn.octopusyan.alistgui.base;
import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager;
-import javafx.application.Platform;
import javafx.concurrent.Task;
+import lombok.Getter;
/**
* @author octopus_yan
@@ -10,6 +10,12 @@ import javafx.concurrent.Task;
public abstract class BaseTask extends Task {
private final ThreadPoolManager Executor = ThreadPoolManager.getInstance();
protected Listener listener;
+ @Getter
+ private final String name;
+
+ protected BaseTask(String name) {
+ this.name = name;
+ }
@Override
protected Void call() throws Exception {
@@ -18,10 +24,6 @@ public abstract class BaseTask extends Task {
return null;
}
- protected void runLater(Runnable runnable) {
- Platform.runLater(runnable);
- }
-
protected abstract void task() throws Exception;
public void onListen(Listener listener) {
diff --git a/src/main/java/cn/octopusyan/alistgui/base/BaseViewModel.java b/src/main/java/cn/octopusyan/alistgui/base/BaseViewModel.java
new file mode 100644
index 0000000..a3b3e23
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/base/BaseViewModel.java
@@ -0,0 +1,13 @@
+package cn.octopusyan.alistgui.base;
+
+import lombok.Data;
+
+/**
+ * View Model
+ *
+ * @author octopus_yan
+ */
+@Data
+public abstract class BaseViewModel {
+
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/config/Context.java b/src/main/java/cn/octopusyan/alistgui/config/Context.java
index 3df7a91..0db021e 100644
--- a/src/main/java/cn/octopusyan/alistgui/config/Context.java
+++ b/src/main/java/cn/octopusyan/alistgui/config/Context.java
@@ -1,6 +1,7 @@
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;
@@ -9,6 +10,7 @@ import cn.octopusyan.alistgui.controller.SetupController;
import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.util.FxmlUtil;
+import cn.octopusyan.alistgui.util.ProcessesUtil;
import javafx.application.Platform;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.IntegerProperty;
@@ -24,7 +26,7 @@ import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.io.IOException;
+import java.io.File;
import java.net.URL;
import java.util.*;
@@ -34,9 +36,11 @@ import java.util.*;
* @author octopus_yan
*/
public class Context {
+ @Getter
+ private static Application application;
private static final Logger log = LoggerFactory.getLogger(Context.class);
private static Scene scene;
- private static final IntegerProperty currentViewIndexProperty = new SimpleIntegerProperty(0);
+ private static final IntegerProperty currentViewIndex = new SimpleIntegerProperty(0);
private static final ObjectProperty theme = new SimpleObjectProperty<>(ConfigManager.theme());
/**
@@ -84,6 +88,10 @@ public class Context {
};
}
+ public static void setApplication(Application application) {
+ Context.application = application;
+ }
+
public static ObjectProperty themeProperty() {
return theme;
}
@@ -121,8 +129,7 @@ public class Context {
LANGUAGE_RESOURCE_FACTORY.setResourceBundle(ResourceBundle.getBundle(LANGUAGE_RESOURCE_NAME, locale));
log.info("language changed to {}", locale);
- if (ConsoleLog.isInit())
- ConsoleLog.info(STR."language changed to \{locale}");
+ ConsoleLog.info(STR."language changed to \{locale}");
}
/**
@@ -147,13 +154,6 @@ public class Context {
* 初始化 语言
*/
private static void initI18n() {
- currentLocaleProperty().addListener((_, _, locale) -> Platform.runLater(() -> {
- try {
- loadScene();
- } catch (IOException e) {
- log.error("", e);
- }
- }));
}
/**
@@ -171,45 +171,58 @@ public class Context {
* 初始化场景
*
* @return Scene
- * @throws IOException 如果在加载过程中发生错误
*/
- public static Scene initScene() throws IOException {
- initI18n();
+ public static Scene initScene() {
+ // locale监听; 切换后,重新加载界面
+ currentLocaleProperty().addListener((_, _, locale) -> Platform.runLater(Context::loadScene));
+ // 加载
loadScene();
return scene;
}
- private static void loadScene() throws IOException {
- FXMLLoader loader = FxmlUtil.load("root-view");
- loader.setControllerFactory(Context.getControlFactory());
- //底层面板
- Pane root = loader.load();
- Optional.ofNullable(scene).ifPresentOrElse(
- s -> s.setRoot(root),
- () -> {
- scene = new Scene(root, root.getPrefWidth() + 20, root.getPrefHeight() + 20, Color.TRANSPARENT);
- URL resource = Objects.requireNonNull(Context.class.getResource("/css/root-view.css"));
- scene.getStylesheets().addAll(resource.toExternalForm());
- scene.setFill(Color.TRANSPARENT);
- }
- );
+ private static void loadScene() {
+ try {
+ FXMLLoader loader = FxmlUtil.load("root-view");
+ loader.setControllerFactory(Context.getControlFactory());
+ //底层面板
+ Pane root = loader.load();
+ Optional.ofNullable(scene).ifPresentOrElse(
+ s -> s.setRoot(root),
+ () -> {
+ scene = new Scene(root, root.getPrefWidth() + 20, root.getPrefHeight() + 20, Color.TRANSPARENT);
+ URL resource = Objects.requireNonNull(Context.class.getResource("/css/root-view.css"));
+ scene.getStylesheets().addAll(resource.toExternalForm());
+ scene.setFill(Color.TRANSPARENT);
+ }
+ );
+ } catch (Throwable e) {
+ log.error("loadScene error", e);
+ }
}
- /**
- * 设置当前展示的界面
- *
- * @param index 界面Index
- */
- public static void setCurrentViewIndex(Number index) {
- currentViewIndexProperty.setValue(index);
+ public static int currentViewIndex() {
+ return currentViewIndex.get();
}
- /**
- * 获取当前展示的界面Index
- *
- * @return 界面Index
- */
- public static Integer getCurrentViewIndex() {
- return currentViewIndexProperty.get();
+ public static IntegerProperty currentViewIndexProperty() {
+ return currentViewIndex;
+ }
+
+ public static void openUrl(String url) {
+ getApplication().getHostServices().showDocument(url);
+ }
+
+ public static void openFolder(File file) {
+ openFile(file);
+ }
+
+ public static void openFile(File file) {
+ if (!file.exists()) return;
+
+ if (file.isDirectory()) {
+ ProcessesUtil.init(file.getAbsolutePath()).exec("explorer.exe .");
+ } else {
+ ProcessesUtil.init(file.getParentFile().getAbsolutePath()).exec(STR."explorer.exe /select,\{file.getName()}");
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/AboutController.java b/src/main/java/cn/octopusyan/alistgui/controller/AboutController.java
index 2c99384..17c45f5 100644
--- a/src/main/java/cn/octopusyan/alistgui/controller/AboutController.java
+++ b/src/main/java/cn/octopusyan/alistgui/controller/AboutController.java
@@ -2,9 +2,9 @@ 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;
@@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory;
*
* @author octopus_yan
*/
-public class AboutController extends BaseController {
+public class AboutController extends BaseController {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@FXML
@@ -23,10 +23,6 @@ public class AboutController extends BaseController {
@FXML
public Label aListVersion;
- @FXML
- public Button checkAppVersion;
-
- private final AboutViewModule viewModule = new AboutViewModule();
@Override
public VBox getRootPanel() {
@@ -45,11 +41,18 @@ public class AboutController extends BaseController {
@Override
public void initViewAction() {
- aListVersion.textProperty().bindBidirectional(viewModule.aListVersionProperty());
+ aListVersion.textProperty().bindBidirectional(viewModel.aListVersionProperty());
}
@FXML
public void checkAListUpdate() {
- viewModule.checkUpdate(ConfigManager.aList());
+ viewModel.checkUpdate(ConfigManager.aList());
}
+
+ @FXML
+ public void checkGuiUpdate() {
+ // TODO 检查 gui 版本
+ AlertUtil.info("待开发。。。").show();
+ }
+
}
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/MainController.java b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java
index 9be02b9..a345d1d 100644
--- a/src/main/java/cn/octopusyan/alistgui/controller/MainController.java
+++ b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java
@@ -4,20 +4,25 @@ import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.AListManager;
import cn.octopusyan.alistgui.manager.ConsoleLog;
-import javafx.application.Platform;
+import cn.octopusyan.alistgui.util.FxmlUtil;
+import cn.octopusyan.alistgui.viewModel.MainViewModel;
import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
+import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.IOException;
+
/**
* 主界面控制器
*
* @author octopus_yan
*/
-public class MainController extends BaseController {
+public class MainController extends BaseController {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@FXML
@@ -30,6 +35,10 @@ public class MainController extends BaseController {
public Button statusLabel;
@FXML
public Button startButton;
+ @FXML
+ public MenuItem browserButton;
+
+ private PasswordController controller;
@Override
public VBox getRootPanel() {
@@ -43,31 +52,18 @@ public class MainController extends BaseController {
@Override
public void initViewStyle() {
- AListManager.runningProperty().addListener(_ -> setStartButton(AListManager.isRunning()));
}
@Override
public void initViewAction() {
- AListManager.runningProperty().addListener((_, _, running) -> setStartButton(running));
- }
-
- private void setStartButton(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(() -> {
- startButton.getStyleClass().remove(removeStyle);
- startButton.getStyleClass().add(addStyle);
- startButton.textProperty().set(button);
-
- statusLabel.getStyleClass().remove(addStyle);
- statusLabel.getStyleClass().add(removeStyle);
- statusLabel.textProperty().set(status);
- });
+ 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()) {
@@ -77,8 +73,41 @@ public class MainController extends BaseController {
}
}
+ // password button
+ @FXML
+ public void adminPassword() throws IOException {
+ if (controller == null) {
+ FXMLLoader load = FxmlUtil.load("admin-panel");
+ load.load();
+ controller = load.getController();
+ }
+ controller.show();
+ }
+
+ // 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();
+ }
}
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/PasswordController.java b/src/main/java/cn/octopusyan/alistgui/controller/PasswordController.java
new file mode 100644
index 0000000..64134b9
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/controller/PasswordController.java
@@ -0,0 +1,127 @@
+package cn.octopusyan.alistgui.controller;
+
+import atlantafx.base.controls.Popover;
+import cn.hutool.core.swing.clipboard.ClipboardUtil;
+import cn.octopusyan.alistgui.base.BaseController;
+import cn.octopusyan.alistgui.config.Context;
+import cn.octopusyan.alistgui.manager.AListManager;
+import cn.octopusyan.alistgui.viewModel.AdminPanelViewModel;
+import javafx.beans.value.ChangeListener;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.PasswordField;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.text.Text;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 管理员密码
+ *
+ * @author octopus_yan
+ */
+public class PasswordController extends BaseController {
+ @FXML
+ private AnchorPane adminPanel;
+
+ @FXML
+ public TextField usernameField;
+ @FXML
+ public Button copyUsername;
+ @FXML
+ public PasswordField passwordField;
+ @FXML
+ public Button refreshPassword;
+ @FXML
+ public Button savePassword;
+ @FXML
+ public Button copyPassword;
+
+ private RootController root;
+
+ private final Popover pop = new Popover(new Text(Context.getLanguageBinding("msg.alist.pwd.copy").get()));
+
+ @Override
+ public Pane getRootPanel() {
+ return adminPanel;
+ }
+
+ @Override
+ public void initData() {
+ root = (RootController) Context.getControllers().get("RootController");
+ }
+
+ @Override
+ public void initViewStyle() {
+ pop.setArrowLocation(Popover.ArrowLocation.BOTTOM_CENTER);
+ }
+
+ @Override
+ public void initViewAction() {
+ passwordField.textProperty().bindBidirectional(viewModel.passwordProperty());
+ passwordField.setOnMouseClicked(event -> {
+ // 点击密码框时,设置为可修改状态
+ passwordField.setEditable(true);
+ refreshPassword.setVisible(true);
+ refreshPassword.setManaged(true);
+ });
+ ChangeListener changeListener = (_, _, focused) -> {
+ if (!focused && !refreshPassword.isFocused()
+ && !copyPassword.isFocused()
+ && StringUtils.equals(passwordField.getText(), AListManager.passwordProperty().get())) {
+ // 当密码栏失去焦点,如果密码未变更,设置为不可用状态
+ passwordField.setEditable(false);
+ refreshPassword.setVisible(false);
+ refreshPassword.setManaged(false);
+ savePassword.setVisible(false);
+ savePassword.setManaged(false);
+ }
+ };
+ passwordField.focusedProperty().addListener(changeListener);
+ refreshPassword.focusedProperty().addListener(changeListener);
+ savePassword.focusedProperty().addListener(changeListener);
+ copyPassword.focusedProperty().addListener(changeListener);
+ // 监听密码修改,展示保存按钮
+ passwordField.textProperty().addListener((_, _, newValue) -> {
+ boolean equals = StringUtils.equals(newValue, AListManager.passwordProperty().get());
+ savePassword.setVisible(!equals);
+ savePassword.setManaged(!equals);
+ });
+ }
+
+ public void show() {
+ root.showModal(getRootPanel(), true);
+ }
+
+ @FXML
+ public void close() {
+ passwordField.setText(AListManager.passwordProperty().get());
+ root.hideModal();
+ }
+
+ @FXML
+ public void copyUsername() {
+ usernameField.copy();
+ pop.show(copyUsername);
+ }
+
+ @FXML
+ public void savePassword(ActionEvent event) {
+ Object source = event.getSource();
+ if (refreshPassword.equals(source)) {
+ AListManager.resetPassword();
+ return;
+ }
+ AListManager.resetPassword(passwordField.getText());
+ savePassword.setVisible(false);
+ savePassword.setManaged(false);
+ }
+
+ @FXML
+ public void copyPassword() {
+ ClipboardUtil.setStr(AListManager.password());
+ pop.show(copyPassword);
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
index 408b5d7..02265f7 100644
--- a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
+++ b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java
@@ -1,32 +1,35 @@
package cn.octopusyan.alistgui.controller;
+import atlantafx.base.controls.ModalPane;
import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context;
+import cn.octopusyan.alistgui.util.WindowsUtil;
+import cn.octopusyan.alistgui.viewModel.RootViewModel;
import com.gluonhq.emoji.EmojiData;
import com.gluonhq.emoji.util.EmojiImageUtils;
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.TabPane;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
-import javafx.scene.layout.VBox;
+import javafx.scene.layout.StackPane;
import org.kordamp.ikonli.javafx.FontIcon;
+import java.util.Locale;
+
/**
- * 主页面控制器
+ * Root 页面控制器
*
* @author octopus_yan@foxmail.com
*/
-public class RootController extends BaseController {
-
- private double xOffset;
- private double yOffset;
-
+public class RootController extends BaseController {
// 布局
@FXML
- private VBox rootPane;
+ private StackPane rootPane;
@FXML
private HBox windowHeader;
@FXML
@@ -48,13 +51,15 @@ public class RootController extends BaseController {
@FXML
public Button sponsor;
+ private final ModalPane modalPane = new ModalPane();
+
/**
* 获取根布局
*
* @return 根布局对象
*/
@Override
- public VBox getRootPanel() {
+ public StackPane getRootPanel() {
return rootPane;
}
@@ -63,7 +68,7 @@ public class RootController extends BaseController {
*/
@Override
public void initData() {
- tabPane.getSelectionModel().select(Context.getCurrentViewIndex());
+ tabPane.getSelectionModel().select(viewModel.currentViewIndexProperty().get());
}
/**
@@ -85,6 +90,16 @@ public class RootController extends BaseController {
ImageView juice = EmojiImageUtils.emojiView(icon, 25);
sponsor.setGraphic(juice);
});
+
+ getRootPanel().getChildren().add(modalPane);
+ modalPane.setId("modalPane");
+ // reset side and transition to reuse a single modal pane between different examples
+ modalPane.displayProperty().addListener((obs, old, val) -> {
+ if (!val) {
+ modalPane.setAlignment(Pos.CENTER);
+ modalPane.usePredefinedTransitionFactories(null);
+ }
+ });
}
/**
@@ -100,17 +115,29 @@ public class RootController extends BaseController {
getWindow().setAlwaysOnTop(newVal);
});
- windowHeader.setOnMousePressed(event -> {
- xOffset = getWindow().getX() - event.getScreenX();
- yOffset = getWindow().getY() - event.getScreenY();
- });
- windowHeader.setOnMouseDragged(event -> {
- getWindow().setX(event.getScreenX() + xOffset);
- getWindow().setY(event.getScreenY() + yOffset);
- });
+ WindowsUtil.bindDragged(windowHeader);
- tabPane.getSelectionModel()
- .selectedIndexProperty()
- .addListener((_, _, newValue) -> Context.setCurrentViewIndex(newValue));
+ 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 showModal(Node node, boolean persistent) {
+ modalPane.show(node);
+ modalPane.setPersistent(persistent);
+ }
+
+ public void hideModal() {
+ modalPane.hide(false);
+ modalPane.setPersistent(false);
}
}
diff --git a/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
index ac81b78..67847f3 100644
--- a/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
+++ b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java
@@ -27,7 +27,7 @@ import java.util.Locale;
*
* @author octopus_yan
*/
-public class SetupController extends BaseController implements Initializable {
+public class SetupController extends BaseController implements Initializable {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@FXML
@@ -51,8 +51,6 @@ public class SetupController extends BaseController implements Initializab
@FXML
public TextField proxyPort;
- private final SetupViewModel viewModule = new SetupViewModel();
-
@Override
public VBox getRootPanel() {
return setupView;
@@ -91,20 +89,19 @@ public class SetupController extends BaseController implements Initializab
@Override
public void initViewAction() {
- autoStartCheckBox.selectedProperty().bindBidirectional(viewModule.autoStartProperty());
- silentStartupCheckBox.selectedProperty().bindBidirectional(viewModule.silentStartupProperty());
- languageComboBox.getSelectionModel().selectedItemProperty()
- .subscribe(locale -> viewModule.languageProperty().set(locale));
- themeComboBox.getSelectionModel().selectedItemProperty()
- .subscribe(theme -> viewModule.themeProperty().set(theme));
- proxySetupComboBox.getSelectionModel().selectedItemProperty()
- .subscribe((setup) -> viewModule.proxySetupProperty().set(setup));
- proxyHost.textProperty().bindBidirectional(viewModule.proxyHostProperty());
- proxyPort.textProperty().bindBidirectional(viewModule.proxyPortProperty());
+ //
+ autoStartCheckBox.selectedProperty().bindBidirectional(viewModel.autoStartProperty());
+ silentStartupCheckBox.selectedProperty().bindBidirectional(viewModel.silentStartupProperty());
+ proxyHost.textProperty().bindBidirectional(viewModel.proxyHostProperty());
+ proxyPort.textProperty().bindBidirectional(viewModel.proxyPortProperty());
+
+ viewModel.languageProperty().bind(languageComboBox.getSelectionModel().selectedItemProperty());
+ viewModel.themeProperty().bind(themeComboBox.getSelectionModel().selectedItemProperty());
+ viewModel.proxySetupProperty().bind(proxySetupComboBox.getSelectionModel().selectedItemProperty());
}
@FXML
public void proxyTest() {
- viewModule.proxyTest();
+ viewModel.proxyTest();
}
}
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/AListManager.java b/src/main/java/cn/octopusyan/alistgui/manager/AListManager.java
index 6b9c91f..5312a06 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/AListManager.java
+++ b/src/main/java/cn/octopusyan/alistgui/manager/AListManager.java
@@ -2,23 +2,87 @@ package cn.octopusyan.alistgui.manager;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context;
+import cn.octopusyan.alistgui.model.AListConfig;
+import cn.octopusyan.alistgui.task.CheckUpdateTask;
+import cn.octopusyan.alistgui.task.DownloadTask;
+import cn.octopusyan.alistgui.task.listener.TaskListener;
+import cn.octopusyan.alistgui.util.DownloadUtil;
import cn.octopusyan.alistgui.util.ProcessesUtil;
+import cn.octopusyan.alistgui.view.alert.AlertUtil;
+import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
/**
* AList 管理
*
* @author octopus_yan
*/
+@Slf4j
public class AListManager {
+ public static final String DATA_DIR = STR."\{Constants.BIN_DIR_PATH}\{File.separator}data";
+ public static final String LOG_DIR = STR."\{DATA_DIR}\{File.separator}log";
+ public static final String CONFIG_FILE = STR."\{DATA_DIR}\{File.separator}config.json";
+
public static final String START_COMMAND = STR."\{Constants.ALIST_FILE} server";
+ public static final String PWD_SET_COMMAND = STR."\{Constants.ALIST_FILE} admin set";
+ public static final String PWD_RANDOM_COMMAND = STR."\{Constants.ALIST_FILE} admin random";
+
+ public static final String DEFAULT_SCHEME = "0.0.0.0:5244";
+ public static final String PASSWORD_MSG_REG = ".*password( is)?: (.*)$";
+ public static AListConfig aListConfig;
+
+ public static final File configFile = new File(CONFIG_FILE);
+
private static final ProcessesUtil util;
private static final BooleanProperty running = new SimpleBooleanProperty(false);
+ private static final StringProperty password = new SimpleStringProperty("******");
+ private static DownloadTask downloadTask;
+
+ private static final ProcessesUtil.OnExecuteListener runningListener;
static {
util = ProcessesUtil.init(Constants.BIN_DIR_PATH);
+ loadConfig();
+ runningListener = new ProcessesUtil.OnExecuteListener() {
+ @Override
+ public void onExecute(String msg) {
+ if (hasConfig() && aListConfig == null) loadConfig();
+
+ if (msg.contains("start HTTP server")) {
+ Platform.runLater(() -> running.set(true));
+ }
+
+ ConsoleLog.msg(msg);
+ }
+
+ @Override
+ public void onExecuteSuccess(boolean success) {
+ Platform.runLater(() -> running.set(false));
+ }
+
+ @Override
+ public void onExecuteError(Exception e) {
+ Platform.runLater(() -> running.set(false));
+ log.error("AList error", e);
+ ConsoleLog.error("AList", e.getMessage());
+ }
+ };
+ }
+
+//==============================={ Property }====================================
+
+ private static void loadConfig() {
+ if (hasConfig()) {
+ aListConfig = ConfigManager.loadConfig(CONFIG_FILE, AListConfig.class);
+ }
}
public static BooleanProperty runningProperty() {
@@ -29,31 +93,50 @@ public class AListManager {
return running.get();
}
+ public static boolean hasConfig() {
+ return configFile.exists() && aListConfig != null;
+ }
+
+ public static String scheme() {
+ return hasConfig() ?
+ STR."\{aListConfig.getScheme().getAddress()}:\{aListConfig.getScheme().getHttpPort()}"
+ : DEFAULT_SCHEME;
+ }
+
+ public static StringProperty passwordProperty() {
+ return password;
+ }
+
+ public static String password() {
+ return password.get();
+ }
+
+//================================{ action }====================================
+
+ public static void openConfig() {
+ Context.openFile(new File(CONFIG_FILE));
+ }
+
+ public static void openLogFolder() {
+ Context.openFolder(new File(LOG_DIR));
+ }
+
+ public static void openScheme() {
+ Context.openUrl(STR."http://\{scheme()}");
+ }
+
public static void start() {
- ConsoleLog.info(getText("alist.status.start"));
- if (running.get()) {
+ if (!checkAList()) return;
+
+ if (running.get() || util.isRunning()) {
ConsoleLog.warning(getText("alist.status.start.running"));
return;
}
- running.set(true);
- util.exec(START_COMMAND, new ProcessesUtil.OnExecuteListener() {
- @Override
- public void onExecute(String msg) {
- if (ConsoleLog.isInit())
- ConsoleLog.msg(msg);
- }
+ ConsoleLog.info(getText("alist.status.start"));
- @Override
- public void onExecuteSuccess(int exitValue) {
- running.set(false);
- }
-
- @Override
- public void onExecuteError(Exception e) {
- running.set(false);
- }
- });
+ loadConfig();
+ util.exec(START_COMMAND, runningListener);
}
public static void stop() {
@@ -65,21 +148,96 @@ public class AListManager {
util.destroy();
}
- static ChangeListener changeListener;
+ static ChangeListener restartListener;
public static void restart() {
- stop();
- changeListener = (_, _, run) -> {
- if (run) return;
+ if (!running.get()) {
start();
- if (changeListener != null) {
- running.removeListener(changeListener);
+ } else {
+ stop();
+
+ restartListener = (_, _, run) -> {
+ if (run) return;
+ running.removeListener(restartListener);
+ start();
+ };
+ running.addListener(restartListener);
+ }
+ }
+
+ public static void resetPassword() {
+ resetPassword("");
+ }
+
+ static ChangeListener resetPasswordListener;
+
+ public static void resetPassword(String pwd) {
+ String command = StringUtils.isNoneEmpty(pwd) ?
+ STR."\{PWD_SET_COMMAND} \{pwd}" : PWD_RANDOM_COMMAND;
+
+ if (isRunning()) {
+ util.exec(command, ConsoleLog::msg);
+ return;
+ }
+
+ start();
+ resetPasswordListener = (_, _, newValue) -> {
+ if (newValue) {
+ running.removeListener(resetPasswordListener);
+ util.exec(command, ConsoleLog::msg);
}
};
- running.addListener(changeListener);
+ running.addListener(resetPasswordListener);
+
+ }
+
+//============================={ private }====================================
+
+ /**
+ * TODO 点击开始时检查 aList 执行文件
+ */
+ private static boolean checkAList() {
+ if (new File(Constants.ALIST_FILE).exists()) return true;
+
+ if (downloadTask != null && downloadTask.isRunning()) {
+ ConsoleLog.warning("AList Downloading ...");
+ return false;
+ }
+
+ var task = new CheckUpdateTask(ConfigManager.aList());
+ task.onListen(new TaskListener.UpgradeUpgradeListener(task) {
+ @Override
+ public void onChecked(boolean hasUpgrade, String version) {
+ Platform.runLater(() -> showDownload(version));
+ }
+ });
+ task.execute();
+
+ return false;
+ }
+
+ private static void showDownload(String version) {
+ String content = STR."""
+ \{getText("msg.alist.download.notfile")}
+ \{Context.getLanguageBinding("update.remote").get()} : \{version}
+ """;
+ downloadTask = DownloadUtil.startDownload(ConfigManager.aList(), version, () -> {
+ DownloadUtil.unzip(ConfigManager.aList());
+ Platform.runLater(() -> ConfigManager.aListVersion(version));
+ restart();
+ });
+ AlertUtil.confirm()
+ .title("Download ALst")
+ .header(null)
+ .content(content)
+ .show(downloadTask::execute);
}
private static String getText(String code) {
return Context.getLanguageBinding(code).get();
}
+
+ public static void tmpPassword(String pwd) {
+ Platform.runLater(() -> password.set(pwd));
+ }
}
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java b/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
index 6122a7d..c6a7404 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
+++ b/src/main/java/cn/octopusyan/alistgui/manager/ConfigManager.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import javafx.application.Platform;
+import javafx.beans.property.StringProperty;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@@ -66,7 +67,7 @@ public class ConfigManager {
guiConfig = loadConfig(Constants.GUI_CONFIG_PATH, GuiConfig.class);
}
- private static T loadConfig(String path, Class clazz) {
+ public static T loadConfig(String path, Class clazz) {
File src = new File(path);
try {
if (!src.exists()) {
@@ -250,8 +251,12 @@ public class ConfigManager {
return aList().getVersion();
}
+ public static StringProperty aListVersionProperty() {
+ return aList().versionProperty();
+ }
+
public static void aListVersion(String version) {
- aList().setVersion(version);
+ aListVersionProperty().set(version);
}
public static Gui gui() {
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/ConsoleLog.java b/src/main/java/cn/octopusyan/alistgui/manager/ConsoleLog.java
index f3490e9..cf1cc72 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/ConsoleLog.java
+++ b/src/main/java/cn/octopusyan/alistgui/manager/ConsoleLog.java
@@ -1,12 +1,17 @@
package cn.octopusyan.alistgui.manager;
+import atlantafx.base.controls.Popover;
import atlantafx.base.util.BBCodeParser;
+import cn.hutool.core.swing.clipboard.ClipboardUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
+import cn.octopusyan.alistgui.config.Context;
import javafx.application.Platform;
-import javafx.scene.Node;
+import javafx.scene.control.Hyperlink;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextFlow;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@@ -14,6 +19,7 @@ import org.apache.commons.lang3.time.DateFormatUtils;
import java.util.Arrays;
import java.util.Date;
+import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -27,8 +33,9 @@ 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\\[(\\d+)m(.*)\033\\[0m(.*)$";
- private final static String CONSOLE_COLOR_SUFFIX = "\033[0m";
+ private final static String CONSOLE_COLOR_PREFIX = "^\033[";
+ private final static String CONSOLE_MSG_REX = "^\033\\[(\\d+)m(.*)\033\\[0m(.*)$";
+ private final static String URL_IP_REX = "^((ht|f)tps?:\\/\\/)?[\\w-+&@#/%?=~_|!:,.;]*[\\w-+&@#/%=~_|]+(:\\d{1,5})?\\/?$";
private ConsoleLog(ScrollPane logAreaSp, VBox textArea) {
this.textArea = textArea;
@@ -78,37 +85,104 @@ public class ConsoleLog {
}
public static void msg(String message, Object... param) {
- if (message.contains("\033[")) {
- message = resetConsoleColor(message);
- }
+ if (StringUtils.isEmpty(message) || !isInit()) return;
+ message = message.strip();
+ message = StrUtil.format(message, param);
- Node text = BBCodeParser.createFormattedText(STR."\{StrUtil.format(message, param)}");
- Platform.runLater(() -> log.textArea.getChildren().add(text));
+ // 多颜色消息处理
+ if (StringUtils.countMatches(message, CONSOLE_COLOR_PREFIX) > 1) {
+ String[] split = message.replace(CONSOLE_MSG_REX, "\n%s".formatted(CONSOLE_COLOR_PREFIX)).split("\n");
+ List msgs = Arrays.stream(split).toList();
+ for (String msg : msgs) {
+ msg(msg);
+ }
+ return;
+ }
+ message = setPwdText(message);
+ message = resetConsoleColor(message);
+
+ print(message);
}
public static void printLog(String tag, Level level, String message, Object... param) {
+ if (!isInit()) return;
+ // 时间
String time = DateFormatUtils.format(new Date(), format);
time = STR."[color=-color-accent-emphasis]\{time}[/color]";
-
+ // 级别
String levelStr = resetLevelColor(level);
-
+ // 标签
tag = StringUtils.isEmpty(tag) ? "" : STR."\{tag}: ";
-
+ // 消息
message = STR."[color=-color-fg-muted]\{StrUtil.format(message, param)}[/color]";
- Node text;
- String input = STR."\{time} \{levelStr} \{tag}\{message}";
+ // 拼接后输出
+ String input = STR."\{time} \{levelStr} - \{tag}\{message}";
- if (input.contains("\n")) {
- text = BBCodeParser.createLayout(input);
- } else {
- text = BBCodeParser.createFormattedText(input);
+ print(input);
+ }
+
+ private static void print(String message) {
+
+ // 标记链接
+ String regex = STR.".*(\{AListManager.scheme()}|\{URL_IP_REX}).*";
+ if (ReUtil.isMatch(regex, message)) {
+ String text = ReUtil.get(regex, message, 1);
+ String url = text.startsWith("http") ? STR."http://\{text}" : text;
+ url = url.replace("0.0.0.0", "127.0.0.1");
+ message = message.replace(text, STR."[url=\{url}]\{text}[/url]");
}
+
+ TextFlow text = BBCodeParser.createFormattedText(STR."\{message}");
+ // 处理链接
+ setLink(text);
+
Platform.runLater(() -> log.textArea.getChildren().add(text));
}
-//============================{ 私有方法 }================================
+//==========================================={ 私有方法 }===================================================
+
+ /**
+ * 将密码标记为link,一起处理点击事件
+ *
+ * @param msg 输出信息
+ * @return 处理后的信息
+ */
+ private static String setPwdText(String msg) {
+ if (!ReUtil.isMatch(AListManager.PASSWORD_MSG_REG, msg)) return msg;
+
+ String password = ReUtil.get(AListManager.PASSWORD_MSG_REG, msg, 2);
+ AListManager.tmpPassword(password);
+ return msg.replace(password, STR."[url=\{password}]\{password}[/url]");
+ }
+
+ /**
+ * 处理文本流中的链接
+ *
+ * @param text 文本流
+ */
+ private static void setLink(TextFlow text) {
+
+ text.getChildren().forEach(child -> {
+ switch (child) {
+ case Hyperlink link -> link.setOnAction(_ -> {
+ String linkText = link.getUserData().toString();
+ if (ReUtil.isMatch(URL_IP_REX, linkText)) {
+ Context.getApplication().getHostServices().showDocument(linkText);
+ } else {
+ ClipboardUtil.setStr(linkText);
+ var pop = new Popover(new Text(Context.getLanguageBinding("msg.alist.pwd.copy").get()));
+ pop.show(link);
+ }
+ link.setVisited(false);
+ });
+ case TextFlow flow -> setLink(flow);
+ default -> {
+ }
+ }
+ });
+ }
/**
* 控制台输出颜色
@@ -117,16 +191,13 @@ public class ConsoleLog {
* @return bbcode 颜色文本
*/
private static String resetConsoleColor(String msg) {
- try {
- String colorCode = ReUtil.get(CONSOLE_COLOR_PREFIX, msg, 1);
- String color = StringUtils.lowerCase(Color.valueOf(Integer.parseInt(colorCode)).getColor());
- String colorMsg = ReUtil.get(CONSOLE_COLOR_PREFIX, msg, 2);
- msg = ReUtil.get(CONSOLE_COLOR_PREFIX, msg, 3);
- return color(color, colorMsg) + msg;
- } catch (Throwable e) {
- e.printStackTrace();
- }
- return "";
+ if (!msg.contains("\033[")) return msg;
+
+ String colorCode = ReUtil.get(CONSOLE_MSG_REX, msg, 1);
+ String color = StringUtils.lowerCase(Color.valueOf(Integer.parseInt(colorCode)).getColor());
+ String colorMsg = ReUtil.get(CONSOLE_MSG_REX, msg, 2);
+ msg = ReUtil.get(CONSOLE_MSG_REX, msg, 3);
+ return color(color, colorMsg) + msg;
}
/**
@@ -149,8 +220,8 @@ public class ConsoleLog {
@RequiredArgsConstructor
public enum Level {
INFO("INFO", null),
- WARN("WARN", "color=-color-chart-2"),
- ERROR("ERROR", "color=-color-chart-3"),
+ WARN("WARN", "-color-danger-emphasis"),
+ ERROR("ERROR", "-color-danger-fg"),
;
private final String code;
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
index 65427bc..10fb04e 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
+++ b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
@@ -1,11 +1,14 @@
package cn.octopusyan.alistgui.manager.http;
import cn.octopusyan.alistgui.enums.ProxySetup;
+import cn.octopusyan.alistgui.manager.http.handler.BodyHandler;
import cn.octopusyan.alistgui.model.ProxyInfo;
import cn.octopusyan.alistgui.util.JsonUtil;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
@@ -15,15 +18,19 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
+import java.util.function.BiConsumer;
/**
* 网络请求封装
*
* @author octopus_yan@foxmail.com
*/
+@Slf4j
public class HttpUtil {
private volatile static HttpUtil util;
@Getter
@@ -107,6 +114,30 @@ public class HttpUtil {
return response.body();
}
+ public void download(String url, String savePath, BiConsumer listener) throws IOException, InterruptedException {
+ HttpRequest request = getRequest(url, null).build();
+ // 检查bin目录
+ File binDir = new File(savePath);
+ if (!binDir.exists()) {
+ log.debug(STR."dir [\{savePath}] not exists");
+ //noinspection ResultOfMethodCallIgnored
+ binDir.mkdirs();
+ log.debug(STR."created dir [\{savePath}]");
+ }
+
+ // 下载处理器
+ var handler = BodyHandler.create(
+ Path.of(savePath),
+ StandardOpenOption.CREATE, StandardOpenOption.WRITE
+ );
+
+ // 下载监听
+ if (listener != null)
+ handler.listener(listener);
+
+ HttpResponse response = httpClient.send(request, handler);
+ }
+
private HttpRequest.Builder getRequest(String uri, JsonNode header) {
HttpRequest.Builder request = HttpRequest.newBuilder();
// 请求地址
@@ -141,7 +172,7 @@ public class HttpUtil {
}
}
if (!formParams.isEmpty()) {
- formParams = new StringBuilder("?" + formParams.substring(1));
+ formParams = new StringBuilder(STR."?\{formParams.substring(1)}");
}
return formParams.toString();
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/handler/BodyHandler.java b/src/main/java/cn/octopusyan/alistgui/manager/http/handler/BodyHandler.java
new file mode 100644
index 0000000..913aa6c
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/manager/http/handler/BodyHandler.java
@@ -0,0 +1,102 @@
+package cn.octopusyan.alistgui.manager.http.handler;
+
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.net.http.HttpResponse;
+import java.nio.ByteBuffer;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Flow;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * 下载处理
+ *
+ * @author octopus_yan
+ */
+@Slf4j
+public class BodyHandler implements HttpResponse.BodyHandler {
+ private final HttpResponse.BodyHandler handler;
+ private BiConsumer consumer;
+
+ private BodyHandler(HttpResponse.BodyHandler handler) {
+ this.handler = handler;
+ }
+
+ public static BodyHandler create(Path directory, OpenOption... openOptions) {
+ return new BodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions));
+ }
+
+ @Override
+ public HttpResponse.BodySubscriber apply(HttpResponse.ResponseInfo responseInfo) {
+ AtomicLong length = new AtomicLong(-1);
+ // 获取文件大小
+ Optional string = responseInfo.headers().firstValue("content-length");
+ string.ifPresentOrElse(s -> {
+ length.set(Long.parseLong(s));
+ log.debug(STR."========={content-length = \{s}}=========");
+ }, () -> {
+ String msg = "response not has header [content-length]";
+ log.error(msg);
+ });
+
+ BodySubscriber subscriber = new BodySubscriber(handler.apply(responseInfo));
+ subscriber.setConsumer(progress -> consumer.accept(length.get(), progress));
+
+ return subscriber;
+ }
+
+ public void listener(BiConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ public static class BodySubscriber implements HttpResponse.BodySubscriber {
+ private final HttpResponse.BodySubscriber subscriber;
+ private final AtomicLong progress = new AtomicLong(0);
+ @Setter
+ private Consumer consumer;
+
+ public BodySubscriber(HttpResponse.BodySubscriber subscriber) {
+ this.subscriber = subscriber;
+ }
+
+ @Override
+ public CompletionStage getBody() {
+ return subscriber.getBody();
+ }
+
+ @Override
+ public void onSubscribe(Flow.Subscription subscription) {
+ subscriber.onSubscribe(subscription);
+ }
+
+ @Override
+ public void onNext(List item) {
+ subscriber.onNext(item);
+
+ // 记录进度
+ for (ByteBuffer byteBuffer : item) {
+ progress.addAndGet(byteBuffer.limit());
+ }
+ consumer.accept(progress.get());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ subscriber.onError(throwable);
+ }
+
+ @Override
+ public void onComplete() {
+ subscriber.onComplete();
+
+ consumer.accept(progress.get());
+ }
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/model/AListConfig.java b/src/main/java/cn/octopusyan/alistgui/model/AListConfig.java
new file mode 100644
index 0000000..ff6870d
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/model/AListConfig.java
@@ -0,0 +1,207 @@
+package cn.octopusyan.alistgui.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * alist 配置文件 model
+ *
+ * @author octopus_yan
+ */
+@NoArgsConstructor
+@Getter
+public class AListConfig {
+
+ @JsonProperty("force")
+ private Boolean force;
+ @JsonProperty("site_url")
+ private String siteUrl;
+ @JsonProperty("cdn")
+ private String cdn;
+ @JsonProperty("jwt_secret")
+ private String jwtSecret;
+ @JsonProperty("token_expires_in")
+ private Integer tokenExpiresIn;
+ @JsonProperty("database")
+ private Database database;
+ @JsonProperty("meilisearch")
+ private MeiliSearch meilisearch;
+ @JsonProperty("scheme")
+ private Scheme scheme;
+ @JsonProperty("temp_dir")
+ private String tempDir;
+ @JsonProperty("bleve_dir")
+ private String bleveDir;
+ @JsonProperty("dist_dir")
+ private String distDir;
+ @JsonProperty("log")
+ private Log log;
+ @JsonProperty("delayed_start")
+ private Integer delayedStart;
+ @JsonProperty("max_connections")
+ private Integer maxConnections;
+ @JsonProperty("tls_insecure_skip_verify")
+ private Boolean tlsInsecureSkipVerify;
+ @JsonProperty("tasks")
+ private Tasks tasks;
+ @JsonProperty("cors")
+ private Cors cors;
+ @JsonProperty("s3")
+ private S3 s3;
+
+ @NoArgsConstructor
+ @Getter
+ public static class Database {
+ @JsonProperty("type")
+ private String type;
+ @JsonProperty("host")
+ private String host;
+ @JsonProperty("port")
+ private Integer port;
+ @JsonProperty("user")
+ private String user;
+ @JsonProperty("password")
+ private String password;
+ @JsonProperty("name")
+ private String name;
+ @JsonProperty("db_file")
+ private String dbFile;
+ @JsonProperty("table_prefix")
+ private String tablePrefix;
+ @JsonProperty("ssl_mode")
+ private String sslMode;
+ @JsonProperty("dsn")
+ private String dsn;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class MeiliSearch {
+ @JsonProperty("host")
+ private String host;
+ @JsonProperty("api_key")
+ private String apiKey;
+ @JsonProperty("index_prefix")
+ private String indexPrefix;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Scheme {
+ @JsonProperty("address")
+ private String address;
+ @JsonProperty("http_port")
+ private Integer httpPort;
+ @JsonProperty("https_port")
+ private Integer httpsPort;
+ @JsonProperty("force_https")
+ private Boolean forceHttps;
+ @JsonProperty("cert_file")
+ private String certFile;
+ @JsonProperty("key_file")
+ private String keyFile;
+ @JsonProperty("unix_file")
+ private String unixFile;
+ @JsonProperty("unix_file_perm")
+ private String unixFilePerm;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Log {
+ @JsonProperty("enable")
+ private Boolean enable;
+ @JsonProperty("name")
+ private String name;
+ @JsonProperty("max_size")
+ private Integer maxSize;
+ @JsonProperty("max_backups")
+ private Integer maxBackups;
+ @JsonProperty("max_age")
+ private Integer maxAge;
+ @JsonProperty("compress")
+ private Boolean compress;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Tasks {
+ @JsonProperty("download")
+ private Download download;
+ @JsonProperty("transfer")
+ private Transfer transfer;
+ @JsonProperty("upload")
+ private Upload upload;
+ @JsonProperty("copy")
+ private Copy copy;
+
+ @NoArgsConstructor
+ @Getter
+ public static class Download {
+ @JsonProperty("workers")
+ private Integer workers;
+ @JsonProperty("max_retry")
+ private Integer maxRetry;
+ @JsonProperty("task_persistant")
+ private Boolean taskPersistant;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Transfer {
+ @JsonProperty("workers")
+ private Integer workers;
+ @JsonProperty("max_retry")
+ private Integer maxRetry;
+ @JsonProperty("task_persistant")
+ private Boolean taskPersistant;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Upload {
+ @JsonProperty("workers")
+ private Integer workers;
+ @JsonProperty("max_retry")
+ private Integer maxRetry;
+ @JsonProperty("task_persistant")
+ private Boolean taskPersistant;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Copy {
+ @JsonProperty("workers")
+ private Integer workers;
+ @JsonProperty("max_retry")
+ private Integer maxRetry;
+ @JsonProperty("task_persistant")
+ private Boolean taskPersistant;
+ }
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class Cors {
+ @JsonProperty("allow_origins")
+ private List allowOrigins;
+ @JsonProperty("allow_methods")
+ private List allowMethods;
+ @JsonProperty("allow_headers")
+ private List allowHeaders;
+ }
+
+ @NoArgsConstructor
+ @Getter
+ public static class S3 {
+ @JsonProperty("enable")
+ private Boolean enable;
+ @JsonProperty("port")
+ private Integer port;
+ @JsonProperty("ssl")
+ private Boolean ssl;
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/model/upgrade/AList.java b/src/main/java/cn/octopusyan/alistgui/model/upgrade/AList.java
index 9fa79b5..c1c5690 100644
--- a/src/main/java/cn/octopusyan/alistgui/model/upgrade/AList.java
+++ b/src/main/java/cn/octopusyan/alistgui/model/upgrade/AList.java
@@ -1,6 +1,8 @@
package cn.octopusyan.alistgui.model.upgrade;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
import lombok.Data;
/**
@@ -14,5 +16,17 @@ public class AList implements UpgradeApp {
private final String repo = "alist";
private String releaseFile = "alist-windows-amd64.zip";
- private String version = "unknown";
+ private StringProperty version = new SimpleStringProperty("unknown");
+
+ public StringProperty versionProperty() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version.set(version);
+ }
+
+ public String getVersion() {
+ return version.get();
+ }
}
diff --git a/src/main/java/cn/octopusyan/alistgui/task/CheckUpdateTask.java b/src/main/java/cn/octopusyan/alistgui/task/CheckUpdateTask.java
new file mode 100644
index 0000000..9ba4ba3
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/task/CheckUpdateTask.java
@@ -0,0 +1,43 @@
+package cn.octopusyan.alistgui.task;
+
+import cn.octopusyan.alistgui.base.BaseTask;
+import cn.octopusyan.alistgui.manager.http.HttpUtil;
+import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
+import cn.octopusyan.alistgui.util.JsonUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 检查更新任务
+ *
+ * @author octopus_yan
+ */
+public class CheckUpdateTask extends BaseTask {
+ private final UpgradeApp app;
+
+ public CheckUpdateTask(UpgradeApp app) {
+ super(STR."check update \{app.getRepo()}");
+ this.app = app;
+ }
+
+ @Override
+ protected void task() throws Exception {
+ String responseStr = HttpUtil.getInstance().get(app.getReleaseApi(), null, null);
+ JsonNode response = JsonUtil.parseJsonObject(responseStr);
+
+ // TODO 校验返回内容
+ String newVersion = response.get("tag_name").asText();
+
+ if (listener != null && listener instanceof UpgradeListener lis)
+ lis.onChecked(!StringUtils.equals(app.getVersion(), newVersion), newVersion);
+ }
+
+ public interface UpgradeListener extends BaseTask.Listener {
+ @Override
+ default void onSucceeded() {
+ // do nothing ...
+ }
+
+ void onChecked(boolean hasUpgrade, String version);
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/task/DownloadTask.java b/src/main/java/cn/octopusyan/alistgui/task/DownloadTask.java
index 79e3fa0..6a8104e 100644
--- a/src/main/java/cn/octopusyan/alistgui/task/DownloadTask.java
+++ b/src/main/java/cn/octopusyan/alistgui/task/DownloadTask.java
@@ -3,26 +3,8 @@ 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.Setter;
import lombok.extern.slf4j.Slf4j;
-import java.io.File;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.ByteBuffer;
-import java.nio.file.OpenOption;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Flow;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
/**
* TODO 下载任务
*
@@ -33,126 +15,24 @@ public class DownloadTask extends BaseTask {
private final String downloadUrl;
public DownloadTask(String downloadUrl) {
+ super(STR."Download \{downloadUrl}");
this.downloadUrl = downloadUrl;
- log.info(STR."downlaod url : \{downloadUrl}");
+ }
+
+ public void onListen(DownloadListener listener) {
+ super.onListen(listener);
}
@Override
protected void task() throws Exception {
- HttpClient client = HttpUtil.getInstance().getHttpClient();
- HttpRequest request = HttpRequest.newBuilder().uri(URI.create(downloadUrl)).build();
-
- // 检查bin目录
- File binDir = new File(Constants.BIN_DIR_PATH);
- if (!binDir.exists()) {
- log.debug(STR."dir [\{Constants.BIN_DIR_PATH}] not exists");
- //noinspection ResultOfMethodCallIgnored
- binDir.mkdirs();
- log.debug(STR."created dir [\{Constants.BIN_DIR_PATH}]");
- }
-
- // 下载处理器
- var handler = BodyHandler.create(
- Path.of(Constants.BIN_DIR_PATH),
- StandardOpenOption.CREATE, StandardOpenOption.WRITE
+ HttpUtil.getInstance().download(
+ downloadUrl,
+ Constants.BIN_DIR_PATH,
+ listener instanceof DownloadListener ? ((DownloadListener) listener)::onProgress : null
);
-
- // 下载监听
- handler.listener((total, progress) -> {
- // 输出进度
- if (listener != null && listener instanceof Listener lis)
- lis.onProgress(total, progress);
- });
-
- log.info("download start");
- HttpResponse response = client.send(request, handler);
- log.info("download success");
}
- public interface Listener extends BaseTask.Listener {
+ public interface DownloadListener extends BaseTask.Listener {
void onProgress(Long total, Long progress);
}
-
- /**
- * 自定义下载返回处理
- */
- private static class BodyHandler implements HttpResponse.BodyHandler {
- private final HttpResponse.BodyHandler handler;
- private BiConsumer consumer;
-
- private BodyHandler(HttpResponse.BodyHandler handler) {
- this.handler = handler;
- }
-
- public static BodyHandler create(Path directory, OpenOption... openOptions) {
- return new BodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions));
- }
-
- @Override
- public HttpResponse.BodySubscriber apply(HttpResponse.ResponseInfo responseInfo) {
- AtomicLong length = new AtomicLong(-1);
- // 获取文件大小
- Optional string = responseInfo.headers().firstValue("content-length");
- string.ifPresentOrElse(s -> {
- length.set(Long.parseLong(s));
- log.debug(STR."========={content-length = \{s}}=========");
- }, () -> {
- String msg = "response not has header [content-length]";
- log.error(msg);
- });
-
- BodySubscriber subscriber = new BodySubscriber(handler.apply(responseInfo));
- subscriber.setConsumer(progress -> consumer.accept(length.get(), progress));
-
- return subscriber;
- }
-
- public void listener(BiConsumer consumer) {
- this.consumer = consumer;
- }
- }
-
- public static class BodySubscriber implements HttpResponse.BodySubscriber {
- private final HttpResponse.BodySubscriber subscriber;
- private final AtomicLong progress = new AtomicLong(0);
- @Setter
- private Consumer consumer;
-
- public BodySubscriber(HttpResponse.BodySubscriber subscriber) {
- this.subscriber = subscriber;
- }
-
- @Override
- public CompletionStage getBody() {
- return subscriber.getBody();
- }
-
- @Override
- public void onSubscribe(Flow.Subscription subscription) {
- subscriber.onSubscribe(subscription);
- }
-
- @Override
- public void onNext(List item) {
- subscriber.onNext(item);
-
- // 记录进度
- for (ByteBuffer byteBuffer : item) {
- progress.addAndGet(byteBuffer.limit());
- }
- consumer.accept(progress.get());
- }
-
- @Override
- public void onError(Throwable throwable) {
- subscriber.onError(throwable);
- }
-
- @Override
- public void onComplete() {
- subscriber.onComplete();
-
- consumer.accept(progress.get());
- }
- }
}
diff --git a/src/main/java/cn/octopusyan/alistgui/task/ProxyCheckTask.java b/src/main/java/cn/octopusyan/alistgui/task/ProxyCheckTask.java
index b9e2b9f..0dc3195 100644
--- a/src/main/java/cn/octopusyan/alistgui/task/ProxyCheckTask.java
+++ b/src/main/java/cn/octopusyan/alistgui/task/ProxyCheckTask.java
@@ -14,6 +14,7 @@ public class ProxyCheckTask extends BaseTask {
private final String checkUrl;
public ProxyCheckTask(String checkUrl) {
+ super(STR."ProxyCheck[\{checkUrl}]");
this.checkUrl = checkUrl;
this.updateProgress(0d, 1d);
}
diff --git a/src/main/java/cn/octopusyan/alistgui/task/UpgradeTask.java b/src/main/java/cn/octopusyan/alistgui/task/UpgradeTask.java
deleted file mode 100644
index 7ef9991..0000000
--- a/src/main/java/cn/octopusyan/alistgui/task/UpgradeTask.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package cn.octopusyan.alistgui.task;
-
-import cn.octopusyan.alistgui.base.BaseTask;
-import cn.octopusyan.alistgui.manager.http.HttpUtil;
-import cn.octopusyan.alistgui.model.upgrade.AList;
-import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
-import cn.octopusyan.alistgui.util.JsonUtil;
-import cn.octopusyan.alistgui.viewModel.AboutViewModule;
-import com.fasterxml.jackson.databind.JsonNode;
-import org.apache.commons.lang3.StringUtils;
-
-/**
- * 检查更新任务
- *
- * @author octopus_yan
- */
-public class UpgradeTask extends BaseTask {
- private final AboutViewModule viewModule;
- private final UpgradeApp app;
-
- public UpgradeTask(AboutViewModule viewModel, UpgradeApp app) {
- this.viewModule = viewModel;
- this.app = app;
- }
-
- @Override
- protected void task() throws Exception {
- String responseStr = HttpUtil.getInstance().get(app.getReleaseApi(), null, null);
- JsonNode response = JsonUtil.parseJsonObject(responseStr);
-
- // TODO 校验返回内容
- String newVersion = response.get("tag_name").asText();
-
- runLater(() -> {
- if (app instanceof AList) {
- viewModule.aListUpgradeProperty().setValue(!StringUtils.equals(app.getVersion(), newVersion));
- viewModule.aListNewVersionProperty().setValue(newVersion);
- } else {
- viewModule.guiUpgradeProperty().setValue(!StringUtils.equals(app.getVersion(), newVersion));
- viewModule.guiNewVersionProperty().setValue(newVersion);
- }
- });
- }
-}
diff --git a/src/main/java/cn/octopusyan/alistgui/task/listener/TaskListener.java b/src/main/java/cn/octopusyan/alistgui/task/listener/TaskListener.java
new file mode 100644
index 0000000..03088e4
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/task/listener/TaskListener.java
@@ -0,0 +1,102 @@
+package cn.octopusyan.alistgui.task.listener;
+
+import cn.octopusyan.alistgui.base.BaseTask;
+import cn.octopusyan.alistgui.manager.ConsoleLog;
+import cn.octopusyan.alistgui.task.CheckUpdateTask;
+import cn.octopusyan.alistgui.task.DownloadTask;
+import cn.octopusyan.alistgui.view.alert.AlertUtil;
+import cn.octopusyan.alistgui.view.alert.builder.ProgressBuilder;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 任务监听器默认实现
+ *
+ * @author octopus_yan
+ */
+@Slf4j
+public abstract class TaskListener implements BaseTask.Listener {
+ private final BaseTask task;
+ // 加载弹窗
+ final ProgressBuilder progress = AlertUtil.progress();
+
+ public TaskListener(BaseTask task) {
+ this.task = task;
+ progress.onCancel(task::cancel);
+ }
+
+ @Override
+ public void onStart() {
+ log.info(STR."\{task.getName()} start ...");
+ ConsoleLog.info(task.getName(), "start ...");
+ }
+
+ @Override
+ public void onRunning() {
+ progress.show();
+ }
+
+ @Override
+ public void onCancelled() {
+ progress.close();
+ log.info(STR."\{task.getName()} cancel ...");
+ ConsoleLog.info(task.getName(), "cancel ...");
+ }
+
+ @Override
+ public void onFailed(Throwable throwable) {
+ progress.close();
+ log.error(STR."\{task.getName()} fail ...", throwable);
+ ConsoleLog.error(task.getName(), STR."fail : \{throwable.getMessage()}");
+ onFail(throwable);
+ }
+
+ @Override
+ public void onSucceeded() {
+ progress.close();
+ log.info(STR."\{task.getName()} success ...");
+ ConsoleLog.info(task.getName(), "success ...");
+ onSucceed();
+ }
+
+ protected abstract void onSucceed();
+
+ protected void onFail(Throwable throwable) {
+ }
+
+ /**
+ * 下载任务监听默认实现
+ */
+ public static abstract class DownloadListener extends TaskListener implements DownloadTask.DownloadListener {
+
+ private volatile int lastProgress = 0;
+
+ public DownloadListener(BaseTask task) {
+ super(task);
+ }
+
+ @Override
+ public void onProgress(Long total, Long progress) {
+ int a = (int) (((double) progress / total) * 100);
+ if (a % 10 == 0) {
+ if (a != lastProgress) {
+ lastProgress = a;
+ ConsoleLog.info(STR."\{lastProgress} %");
+ }
+ }
+ }
+ }
+
+ /**
+ * 检查更新监听默认实现
+ */
+ public static abstract class UpgradeUpgradeListener extends TaskListener implements CheckUpdateTask.UpgradeListener {
+ public UpgradeUpgradeListener(BaseTask task) {
+ super(task);
+ }
+
+ @Override
+ protected void onSucceed() {
+ // do nothing ...
+ }
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/DownloadUtil.java b/src/main/java/cn/octopusyan/alistgui/util/DownloadUtil.java
new file mode 100644
index 0000000..406c67e
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/util/DownloadUtil.java
@@ -0,0 +1,84 @@
+package cn.octopusyan.alistgui.util;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.ZipUtil;
+import cn.octopusyan.alistgui.config.Constants;
+import cn.octopusyan.alistgui.manager.ConsoleLog;
+import cn.octopusyan.alistgui.model.upgrade.AList;
+import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
+import cn.octopusyan.alistgui.task.DownloadTask;
+import cn.octopusyan.alistgui.task.listener.TaskListener;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.zip.ZipFile;
+
+/**
+ * 下载工具
+ *
+ * @author octopus_yan
+ */
+@Slf4j
+public class DownloadUtil {
+
+
+ /**
+ * 下载文件
+ *
+ * @param app 应用
+ * @param version 下载版本
+ */
+ public static DownloadTask startDownload(UpgradeApp app, String version, Runnable runnable) {
+ var task = new DownloadTask(app.getDownloadUrl(version));
+ task.onListen(new TaskListener.DownloadListener(task) {
+
+ @Override
+ public void onRunning() {
+ // 不展示进度条
+ }
+
+ @Override
+ public void onSucceed() {
+ String msg = STR."download \{app.getRepo()} success";
+ log.info(msg);
+ ConsoleLog.info(msg);
+
+ runnable.run();
+ }
+ });
+ return task;
+ }
+
+ public static void unzip(UpgradeApp app) {
+ String parentPath = app instanceof AList ? Constants.BIN_DIR_PATH : Constants.DATA_DIR_PATH;
+
+ File file = new File(parentPath + File.separator + app.getReleaseFile());
+ ZipFile zipFile = ZipUtil.toZipFile(file, CharsetUtil.defaultCharset());
+ ZipUtil.read(zipFile, zipEntry -> {
+ String path = zipEntry.getName();
+ if (FileUtil.isWindows()) {
+ // Win系统下
+ path = StrUtil.replace(path, "*", "_");
+ }
+
+ final File outItemFile = FileUtil.file(parentPath, path);
+ if (zipEntry.isDirectory()) {
+ // 目录
+ //noinspection ResultOfMethodCallIgnored
+ outItemFile.mkdirs();
+ } else {
+ InputStream in = ZipUtil.getStream(zipFile, zipEntry);
+ // 文件
+ FileUtil.writeFromStream(in, outItemFile, false);
+
+ log.info(STR."unzip ==> \{outItemFile.getAbsoluteFile()}");
+ }
+ });
+
+ // 解压完成后删除
+ FileUtil.del(file);
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
index 6c1e586..bae8336 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
+++ b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
@@ -4,7 +4,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.*;
import java.io.File;
-import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
@@ -16,20 +15,12 @@ import java.util.Set;
@Slf4j
public class ProcessesUtil {
private static final String NEW_LINE = System.lineSeparator();
+ private static final int EXIT_VALUE = 1;
private final Executor executor = DefaultExecutor.builder().get();
private final ShutdownHookProcessDestroyer processDestroyer;
- private DefaultExecuteResultHandler handler;
- private final PumpStreamHandler streamHandler;
private OnExecuteListener listener;
private CommandLine commandLine;
- private final LogOutputStream logout = new LogOutputStream() {
- @Override
- protected void processLine(String line, int logLevel) {
- if (listener != null)
- listener.onExecute(line + NEW_LINE);
- }
- };
private static final Set set = new HashSet<>();
@@ -37,10 +28,17 @@ public class ProcessesUtil {
* Prevent construction.
*/
private ProcessesUtil(String workingDirectory) {
- streamHandler = new PumpStreamHandler(logout, logout);
+ LogOutputStream logout = new LogOutputStream() {
+ @Override
+ protected void processLine(String line, int logLevel) {
+ if (listener != null)
+ listener.onExecute(line + NEW_LINE);
+ }
+ };
+ PumpStreamHandler streamHandler = new PumpStreamHandler(logout, logout);
executor.setStreamHandler(streamHandler);
executor.setWorkingDirectory(new File(workingDirectory));
- executor.setExitValue(1);
+ executor.setExitValue(EXIT_VALUE);
processDestroyer = new ShutdownHookProcessDestroyer();
executor.setProcessDestroyer(processDestroyer);
}
@@ -51,20 +49,25 @@ public class ProcessesUtil {
return util;
}
- public boolean exec(String command) throws IOException {
+ public boolean exec(String command) {
commandLine = CommandLine.parse(command);
- int execute = executor.execute(commandLine);
- return 0 == execute;
+ int execute = 0;
+ try {
+ execute = executor.execute(commandLine);
+ } catch (Exception e) {
+ log.error("exec", e);
+ }
+ return execute == EXIT_VALUE;
}
public void exec(String command, OnExecuteListener listener) {
this.listener = listener;
commandLine = CommandLine.parse(command);
- handler = new DefaultExecuteResultHandler() {
+ DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() {
@Override
public void onProcessComplete(int exitValue) {
if (listener != null) {
- listener.onExecuteSuccess(exitValue);
+ listener.onExecuteSuccess(exitValue == EXIT_VALUE);
}
}
@@ -77,7 +80,7 @@ public class ProcessesUtil {
};
try {
executor.execute(commandLine, handler);
- } catch (IOException e) {
+ } catch (Exception e) {
if (listener != null) listener.onExecuteError(e);
}
}
@@ -87,6 +90,10 @@ public class ProcessesUtil {
processDestroyer.run();
}
+ public boolean isRunning() {
+ return !processDestroyer.isEmpty();
+ }
+
public static void destroyAll() {
set.forEach(ProcessesUtil::destroy);
}
@@ -94,7 +101,7 @@ public class ProcessesUtil {
public interface OnExecuteListener {
void onExecute(String msg);
- default void onExecuteSuccess(int exitValue) {
+ default void onExecuteSuccess(boolean success) {
}
default void onExecuteError(Exception e) {
diff --git a/src/main/java/cn/octopusyan/alistgui/manager/WindowsUtil.java b/src/main/java/cn/octopusyan/alistgui/util/WindowsUtil.java
similarity index 75%
rename from src/main/java/cn/octopusyan/alistgui/manager/WindowsUtil.java
rename to src/main/java/cn/octopusyan/alistgui/util/WindowsUtil.java
index 7634f4c..9dc750b 100644
--- a/src/main/java/cn/octopusyan/alistgui/manager/WindowsUtil.java
+++ b/src/main/java/cn/octopusyan/alistgui/util/WindowsUtil.java
@@ -1,8 +1,8 @@
-package cn.octopusyan.alistgui.manager;
+package cn.octopusyan.alistgui.util;
+import cn.octopusyan.alistgui.Application;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
-import javafx.stage.Window;
import java.util.HashMap;
import java.util.Map;
@@ -25,7 +25,11 @@ public class WindowsUtil {
}
public static void bindDragged(Pane pane) {
- Window stage = getStage(pane);
+ Stage stage = getStage(pane);
+ bindDragged(pane, stage);
+ }
+
+ public static void bindDragged(Pane pane, Stage stage) {
pane.setOnMousePressed(event -> {
paneXOffset.put(pane, stage.getX() - event.getScreenX());
paneYOffset.put(pane, stage.getY() - event.getScreenY());
@@ -37,6 +41,10 @@ public class WindowsUtil {
}
public static Stage getStage(Pane pane) {
- return (Stage) pane.getScene().getWindow();
+ try {
+ return (Stage) pane.getScene().getWindow();
+ } catch (Throwable e) {
+ return Application.getPrimaryStage();
+ }
}
}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/alert/AlertUtil.java b/src/main/java/cn/octopusyan/alistgui/view/alert/AlertUtil.java
similarity index 92%
rename from src/main/java/cn/octopusyan/alistgui/util/alert/AlertUtil.java
rename to src/main/java/cn/octopusyan/alistgui/view/alert/AlertUtil.java
index be51aa6..9b74557 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/alert/AlertUtil.java
+++ b/src/main/java/cn/octopusyan/alistgui/view/alert/AlertUtil.java
@@ -1,5 +1,6 @@
-package cn.octopusyan.alistgui.util.alert;
+package cn.octopusyan.alistgui.view.alert;
+import cn.octopusyan.alistgui.view.alert.builder.*;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.stage.Stage;
@@ -17,6 +18,10 @@ public class AlertUtil {
AlertUtil.mOwner = stage;
}
+ public static DefaultBuilder builder() {
+ return new DefaultBuilder(mOwner);
+ }
+
public static AlertBuilder info(String content) {
return info().content(content).header(null);
}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/alert/AlertBuilder.java b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/AlertBuilder.java
similarity index 95%
rename from src/main/java/cn/octopusyan/alistgui/util/alert/AlertBuilder.java
rename to src/main/java/cn/octopusyan/alistgui/view/alert/builder/AlertBuilder.java
index 4195713..09e27fe 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/alert/AlertBuilder.java
+++ b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/AlertBuilder.java
@@ -1,5 +1,7 @@
-package cn.octopusyan.alistgui.util.alert;
+package cn.octopusyan.alistgui.view.alert.builder;
+import cn.octopusyan.alistgui.base.BaseBuilder;
+import cn.octopusyan.alistgui.view.alert.AlertUtil;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
diff --git a/src/main/java/cn/octopusyan/alistgui/util/alert/ChoiceBuilder.java b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/ChoiceBuilder.java
similarity index 86%
rename from src/main/java/cn/octopusyan/alistgui/util/alert/ChoiceBuilder.java
rename to src/main/java/cn/octopusyan/alistgui/view/alert/builder/ChoiceBuilder.java
index 7be7c17..b380475 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/alert/ChoiceBuilder.java
+++ b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/ChoiceBuilder.java
@@ -1,9 +1,9 @@
-package cn.octopusyan.alistgui.util.alert;
+package cn.octopusyan.alistgui.view.alert.builder;
+import cn.octopusyan.alistgui.base.BaseBuilder;
import javafx.scene.control.ChoiceDialog;
import javafx.stage.Window;
-import java.util.Collection;
import java.util.Optional;
/**
diff --git a/src/main/java/cn/octopusyan/alistgui/view/alert/builder/DefaultBuilder.java b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/DefaultBuilder.java
new file mode 100644
index 0000000..fcf4317
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/DefaultBuilder.java
@@ -0,0 +1,50 @@
+package cn.octopusyan.alistgui.view.alert.builder;
+
+import cn.octopusyan.alistgui.base.BaseBuilder;
+import cn.octopusyan.alistgui.config.Context;
+import cn.octopusyan.alistgui.util.WindowsUtil;
+import javafx.scene.Node;
+import javafx.scene.control.ButtonBar;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Dialog;
+import javafx.scene.control.DialogPane;
+import javafx.scene.paint.Color;
+import javafx.stage.StageStyle;
+import javafx.stage.Window;
+
+/**
+ * 默认弹窗
+ *
+ * @author octopus_yan
+ */
+public class DefaultBuilder extends BaseBuilder> {
+
+ public DefaultBuilder(Window mOwner) {
+ super(new Dialog<>(), mOwner);
+
+ header(null);
+
+ DialogPane dialogPane = dialog.getDialogPane();
+ dialogPane.getScene().setFill(Color.TRANSPARENT);
+ WindowsUtil.bindDragged(dialogPane);
+ WindowsUtil.bindShadow(dialogPane);
+ WindowsUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT);
+
+ dialogPane.getButtonTypes().add(new ButtonType(
+ Context.getLanguageBinding("label.cancel").get(),
+ ButtonType.CANCEL.getButtonData()
+ ));
+
+ for (Node child : dialogPane.getChildren()) {
+ if (child instanceof ButtonBar) {
+ dialogPane.getChildren().remove(child);
+ break;
+ }
+ }
+ }
+
+ public DefaultBuilder content(Node content) {
+ dialog.getDialogPane().setContent(content);
+ return this;
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/util/alert/ProgressBuilder.java b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/ProgressBuilder.java
similarity index 52%
rename from src/main/java/cn/octopusyan/alistgui/util/alert/ProgressBuilder.java
rename to src/main/java/cn/octopusyan/alistgui/view/alert/builder/ProgressBuilder.java
index 707090d..1cd3fe3 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/alert/ProgressBuilder.java
+++ b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/ProgressBuilder.java
@@ -1,16 +1,13 @@
-package cn.octopusyan.alistgui.util.alert;
+package cn.octopusyan.alistgui.view.alert.builder;
import cn.octopusyan.alistgui.config.Context;
-import cn.octopusyan.alistgui.manager.WindowsUtil;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
-import javafx.scene.Node;
-import javafx.scene.control.*;
+import javafx.scene.control.Button;
+import javafx.scene.control.ProgressBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
-import javafx.scene.paint.Color;
-import javafx.stage.StageStyle;
import javafx.stage.Window;
/**
@@ -18,34 +15,11 @@ import javafx.stage.Window;
*
* @author octopus_yan
*/
-public class ProgressBuilder extends BaseBuilder> {
+public class ProgressBuilder extends DefaultBuilder {
public ProgressBuilder(Window mOwner) {
- this(new Dialog<>(), mOwner);
- }
-
- public ProgressBuilder(Dialog dialog, Window mOwner) {
- super(dialog, mOwner);
-
- DialogPane dialogPane = dialog.getDialogPane();
- dialogPane.getScene().setFill(Color.TRANSPARENT);
- WindowsUtil.bindDragged(dialogPane);
- WindowsUtil.bindShadow(dialogPane);
- WindowsUtil.getStage(dialogPane).initStyle(StageStyle.TRANSPARENT);
- var content = getContent();
-
- dialogPane.setContent(content);
- dialogPane.getButtonTypes().add(new ButtonType(
- Context.getLanguageBinding("label.cancel").get(),
- ButtonType.CANCEL.getButtonData()
- ));
-
- for (Node child : dialogPane.getChildren()) {
- if (child instanceof ButtonBar) {
- dialogPane.getChildren().remove(child);
- break;
- }
- }
+ super(mOwner);
+ content(getContent());
}
private Pane getContent() {
diff --git a/src/main/java/cn/octopusyan/alistgui/util/alert/TextInputBuilder.java b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/TextInputBuilder.java
similarity index 89%
rename from src/main/java/cn/octopusyan/alistgui/util/alert/TextInputBuilder.java
rename to src/main/java/cn/octopusyan/alistgui/view/alert/builder/TextInputBuilder.java
index b89058f..cb77faa 100644
--- a/src/main/java/cn/octopusyan/alistgui/util/alert/TextInputBuilder.java
+++ b/src/main/java/cn/octopusyan/alistgui/view/alert/builder/TextInputBuilder.java
@@ -1,5 +1,6 @@
-package cn.octopusyan.alistgui.util.alert;
+package cn.octopusyan.alistgui.view.alert.builder;
+import cn.octopusyan.alistgui.base.BaseBuilder;
import javafx.scene.control.TextInputDialog;
import javafx.stage.Window;
diff --git a/src/main/java/cn/octopusyan/alistgui/viewModel/AboutViewModule.java b/src/main/java/cn/octopusyan/alistgui/viewModel/AboutViewModule.java
index a7d35c0..c5dbdef 100644
--- a/src/main/java/cn/octopusyan/alistgui/viewModel/AboutViewModule.java
+++ b/src/main/java/cn/octopusyan/alistgui/viewModel/AboutViewModule.java
@@ -1,35 +1,27 @@
package cn.octopusyan.alistgui.viewModel;
-import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.util.CharsetUtil;
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.core.util.ZipUtil;
-import cn.octopusyan.alistgui.base.BaseTask;
-import cn.octopusyan.alistgui.config.Constants;
+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.DownloadTask;
-import cn.octopusyan.alistgui.task.UpgradeTask;
-import cn.octopusyan.alistgui.util.alert.AlertBuilder;
-import cn.octopusyan.alistgui.util.alert.AlertUtil;
+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;
-import java.io.File;
-import java.io.InputStream;
-import java.util.zip.ZipFile;
-
/**
* 关于
*
* @author octopus_yan
*/
@Slf4j
-public class AboutViewModule {
+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);
@@ -38,7 +30,7 @@ public class AboutViewModule {
private final BooleanProperty guiUpgrade = new SimpleBooleanProperty(false);
public AboutViewModule() {
- aListVersion.addListener((_, _, newValue) -> ConfigManager.aListVersion(newValue));
+ aListVersion.bindBidirectional(ConfigManager.aListVersionProperty());
}
public Property aListVersionProperty() {
@@ -83,10 +75,12 @@ public class AboutViewModule {
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)
@@ -95,131 +89,50 @@ public class AboutViewModule {
\{newLabel} : \{newVersion}
""")
.show(() -> {
- if (upgrade) startDownload(app, newVersion);
+ // 可升级,且点击了确定后,开始下载任务
+ 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 UpgradeTask(this, app);
-
- // 加载弹窗
- final var progress = AlertUtil.progress();
- progress.title(Context.getLanguageBinding("proxy.test.title").get());
- progress.onCancel(task::cancel);
+ var task = new CheckUpdateTask(app);
// 任务监听
- task.onListen(new BaseTask.Listener() {
+ task.onListen(new TaskListener.UpgradeUpgradeListener(task) {
@Override
- public void onStart() {
- String msg = STR."start update \{app.getRepo()}...";
- log.info(msg);
- ConsoleLog.info(msg);
- }
-
- @Override
- public void onRunning() {
- progress.show();
- }
-
- @Override
- public void onSucceeded() {
- progress.close();
+ protected void onSucceed() {
if (runnable != null) runnable.run();
}
@Override
- public void onFailed(Throwable throwable) {
- String msg = STR."\{app.getRepo()} check version error";
- log.error(msg, throwable);
- ConsoleLog.error(STR."\{msg} : \{throwable.getMessage()}");
+ 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();
}
-
- /**
- * 下载文件
- *
- * @param app 应用
- * @param version 下载版本
- */
- private void startDownload(UpgradeApp app, String version) {
- DownloadTask downloadTask = new DownloadTask(app.getDownloadUrl(version));
- downloadTask.onListen(new DownloadTask.Listener() {
-
- private volatile int lastProgress = 0;
-
- @Override
- public void onStart() {
- String msg = STR."download \{app.getRepo()} start";
- log.info(msg);
- ConsoleLog.info(msg);
- }
-
- @Override
- public void onProgress(Long total, Long progress) {
- int a = (int) (((double) progress / total) * 100);
- if (a % 10 == 0) {
- if (a != lastProgress) {
- lastProgress = a;
- String msg = STR."\{app.getRepo()} \{a} %";
- log.info(STR."download \{msg}");
- ConsoleLog.info(msg);
- }
- }
- }
-
- @Override
- public void onSucceeded() {
- String msg = STR."download \{app.getRepo()} success";
- log.info(msg);
- ConsoleLog.info(msg);
-
- // 解压并删除文件
- unzip(app);
-
- Platform.runLater(() -> aListVersion.setValue(aListNewVersion.getValue()));
- }
-
- @Override
- public void onFailed(Throwable throwable) {
- log.error("下载失败", throwable);
- ConsoleLog.error(STR."下载失败 => \{throwable.getMessage()}");
- }
- });
- downloadTask.execute();
- }
-
- private void unzip(UpgradeApp app) {
- File file = new File(Constants.BIN_DIR_PATH + File.separator + app.getReleaseFile());
- ZipFile zipFile = ZipUtil.toZipFile(file, CharsetUtil.defaultCharset());
- ZipUtil.read(zipFile, zipEntry -> {
- String path = zipEntry.getName();
- if (FileUtil.isWindows()) {
- // Win系统下
- path = StrUtil.replace(path, "*", "_");
- }
-
- final File outItemFile = FileUtil.file(Constants.BIN_DIR_PATH, path);
- if (zipEntry.isDirectory()) {
- // 目录
- //noinspection ResultOfMethodCallIgnored
- outItemFile.mkdirs();
- } else {
- InputStream in = ZipUtil.getStream(zipFile, zipEntry);
- // 文件
- FileUtil.writeFromStream(in, outItemFile, false);
-
- log.info(STR."unzip ==> \{outItemFile.getAbsoluteFile()}");
- }
- });
-
- // 解压完成后删除
- FileUtil.del(file);
- }
}
diff --git a/src/main/java/cn/octopusyan/alistgui/viewModel/AdminPanelViewModel.java b/src/main/java/cn/octopusyan/alistgui/viewModel/AdminPanelViewModel.java
new file mode 100644
index 0000000..4c2f241
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/viewModel/AdminPanelViewModel.java
@@ -0,0 +1,23 @@
+package cn.octopusyan.alistgui.viewModel;
+
+import cn.octopusyan.alistgui.base.BaseViewModel;
+import cn.octopusyan.alistgui.manager.AListManager;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+
+/**
+ * Admin Panel VM
+ *
+ * @author octopus_yan
+ */
+public class AdminPanelViewModel extends BaseViewModel {
+ private final StringProperty password = new SimpleStringProperty(AListManager.passwordProperty().get());
+
+ public AdminPanelViewModel() {
+ AListManager.passwordProperty().subscribe(password::set);
+ }
+
+ public StringProperty passwordProperty() {
+ return password;
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/viewModel/MainViewModel.java b/src/main/java/cn/octopusyan/alistgui/viewModel/MainViewModel.java
new file mode 100644
index 0000000..17fcf9e
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/viewModel/MainViewModel.java
@@ -0,0 +1,68 @@
+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 startBtnStyleCss = new SimpleListProperty<>(FXCollections.observableArrayList());
+ private final StringProperty startBtnText = new SimpleStringProperty();
+ private final ListProperty 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 startBtnStyleCssProperty() {
+ return startBtnStyleCss;
+ }
+
+ public StringProperty startBtnTextProperty() {
+ return startBtnText;
+ }
+
+ public ListProperty 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);
+ });
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/viewModel/RootViewModel.java b/src/main/java/cn/octopusyan/alistgui/viewModel/RootViewModel.java
new file mode 100644
index 0000000..cad967a
--- /dev/null
+++ b/src/main/java/cn/octopusyan/alistgui/viewModel/RootViewModel.java
@@ -0,0 +1,23 @@
+package cn.octopusyan.alistgui.viewModel;
+
+import cn.octopusyan.alistgui.base.BaseViewModel;
+import cn.octopusyan.alistgui.config.Context;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+
+/**
+ * Root VM
+ *
+ * @author octopus_yan
+ */
+public class RootViewModel extends BaseViewModel {
+ private final IntegerProperty currentViewIndex = new SimpleIntegerProperty(Context.currentViewIndex()) {
+ {
+ Context.currentViewIndexProperty().bind(this);
+ }
+ };
+
+ public IntegerProperty currentViewIndexProperty() {
+ return currentViewIndex;
+ }
+}
diff --git a/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java b/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java
index 0bf2610..4f58c53 100644
--- a/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java
+++ b/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java
@@ -1,14 +1,14 @@
package cn.octopusyan.alistgui.viewModel;
import atlantafx.base.theme.Theme;
-import cn.octopusyan.alistgui.base.BaseTask;
+import cn.octopusyan.alistgui.base.BaseViewModel;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.enums.ProxySetup;
import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.http.HttpUtil;
import cn.octopusyan.alistgui.task.ProxyCheckTask;
-import cn.octopusyan.alistgui.util.alert.AlertUtil;
-import javafx.application.Platform;
+import cn.octopusyan.alistgui.task.listener.TaskListener;
+import cn.octopusyan.alistgui.view.alert.AlertUtil;
import javafx.beans.property.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -21,7 +21,7 @@ import java.util.Locale;
* @author octopus_yan
*/
@Slf4j
-public class SetupViewModel {
+public class SetupViewModel extends BaseViewModel {
private final BooleanProperty autoStart = new SimpleBooleanProperty(ConfigManager.autoStart());
private final BooleanProperty silentStartup = new SimpleBooleanProperty(ConfigManager.silentStartup());
private final ObjectProperty theme = new SimpleObjectProperty<>(ConfigManager.theme());
@@ -96,24 +96,15 @@ public class SetupViewModel {
private static ProxyCheckTask getProxyCheckTask(String checkUrl) {
var task = new ProxyCheckTask(checkUrl);
- final var progress = AlertUtil.progress();
- progress.title(Context.getLanguageBinding("proxy.test.title").get());
- task.onListen(new BaseTask.Listener() {
+ task.onListen(new TaskListener(task) {
@Override
- public void onRunning() {
- progress.onCancel(task::cancel).show();
- }
-
- @Override
- public void onSucceeded() {
- Platform.runLater(progress::close);
+ public void onSucceed() {
AlertUtil.info(Context.getLanguageBinding("proxy.test.result.success").getValue()).show();
}
@Override
- public void onFailed(Throwable throwable) {
- Platform.runLater(progress::close);
+ public void onFail(Throwable throwable) {
final var tmp = Context.getLanguageBinding("proxy.test.result.failed").getValue();
String throwableMessage = throwable.getMessage();
AlertUtil.error(tmp + (StringUtils.isEmpty(throwableMessage) ? "" : throwableMessage)).show();
diff --git a/src/main/resources/css/admin-panel.scss b/src/main/resources/css/admin-panel.scss
new file mode 100644
index 0000000..19ea935
--- /dev/null
+++ b/src/main/resources/css/admin-panel.scss
@@ -0,0 +1,31 @@
+/**************************************************
+ * Admin Password Panel
+**************************************************/
+
+#admin-panel {
+ -fx-background-color: -color-bg-default;
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+
+ .header {
+ .label {
+ -fx-font-size: 20;
+ }
+
+ .button, .button.ikonli-font-icon {
+ -fx-background-radius: 15;
+ }
+ }
+
+ .admin-field {
+ -fx-spacing: 10;
+
+ .text-field {
+ -fx-pref-width: 100;
+ }
+
+ &-value {
+ //-fx-padding: 5 10;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/css/main-view.scss b/src/main/resources/css/main-view.scss
index e23de78..2adf228 100644
--- a/src/main/resources/css/main-view.scss
+++ b/src/main/resources/css/main-view.scss
@@ -14,24 +14,27 @@
-fx-border-radius: 10;
}
-.control-menu, #startButton, #passwordButton, #restartButton, #moreButton {
+.control-menu, #moreButton {
-fx-font-size: 15;
-fx-background-radius: 15;
- -fx-padding: 10 40 10 40;
+ -fx-padding: 10 40;
-fx-border-radius: 15;
-fx-border-width: 2;
}
#startButton {
+ -color-button-bg-hover: -color-button-bg;
+ -color-button-bg-focused: -color-button-bg;
-fx-border-color: -color-button-bg;
+ -fx-border-color-hover: -color-button-bg;
}
#passwordButton {
-color-button-bg: -color-success-3;
-color-button-bg-hover: -color-button-bg;
- -color-button-bg-focused: -color-button-bg;
+ -color-button-bg-focused: -color-success-3;
-color-button-bg-pressed: -color-button-bg;
- -fx-border-color: -color-button-bg;
+ -fx-border-color: -color-success-3;
}
#restartButton {
@@ -43,12 +46,20 @@
}
#moreButton {
- -color-button-bg-focused: transparent;
- -color-button-bg-pressed: transparent;
- -color-button-bg-hover: -color-chart-6-alpha20;
- -color-button-border: -color-chart-6;
- -fx-border-color: -color-chart-6;
- -color-button-border-hover: -color-chart-6-alpha70;
+ -fx-padding: 2 30;
+
+ -fx-background-color: transparent;
+ -fx-border-color: -color-chart-6-alpha70;
+
+ &:hover {
+ -fx-background-color: -color-chart-6-alpha20;
+ -fx-border-color: -color-chart-6-alpha70;
+ }
+
+ .context-menu, .menu-item {
+ -fx-background-radius: 15;
+ -fx-border-radius: 15;
+ }
}
.logArea {
diff --git a/src/main/resources/css/root-view.scss b/src/main/resources/css/root-view.scss
index 7e60841..bdfe4cd 100644
--- a/src/main/resources/css/root-view.scss
+++ b/src/main/resources/css/root-view.scss
@@ -91,23 +91,22 @@
#windowFooter {
.button {
-fx-font-size: 15;
- -fx-background-color: transparent;
-fx-text-alignment: CENTER;
}
- #document {
- -fx-text-fill: -color-success-5;
- }
-
- #github {
- -fx-text-fill: -color-accent-5;
- }
-
- #sponsor {
- -fx-text-fill: -color-danger-emphasis;
- }
-
.ikonli-font-icon {
-fx-font-size: 15;
}
+}
+
+/**************************************************
+ * Modal Pane
+**************************************************/
+
+.modal-pane {
+ -fx-background-radius: 15;
+
+ .scrollable-content {
+ -fx-background-radius: 15;
+ }
}
\ No newline at end of file
diff --git a/src/main/resources/css/root.scss b/src/main/resources/css/root.scss
index 3cd0718..a7a1b30 100644
--- a/src/main/resources/css/root.scss
+++ b/src/main/resources/css/root.scss
@@ -1,10 +1,12 @@
/**************************************************
* Root
**************************************************/
+.root {
+ -fx-font-size: 15;
+ -fx-font-weight: bolder;
+}
.root-pane {
- -fx-font-size: 15;
- -fx-font-weight: normal;
-fx-background-radius: 15;
-fx-border-radius: 15;
diff --git a/src/main/resources/fxml/about-view.fxml b/src/main/resources/fxml/about-view.fxml
index 8bb3008..be018a1 100644
--- a/src/main/resources/fxml/about-view.fxml
+++ b/src/main/resources/fxml/about-view.fxml
@@ -29,6 +29,6 @@
-
-
+
+
diff --git a/src/main/resources/fxml/admin-panel.fxml b/src/main/resources/fxml/admin-panel.fxml
new file mode 100644
index 0000000..8cd586d
--- /dev/null
+++ b/src/main/resources/fxml/admin-panel.fxml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/main-view.fxml b/src/main/resources/fxml/main-view.fxml
index 92a3036..8bc5eea 100644
--- a/src/main/resources/fxml/main-view.fxml
+++ b/src/main/resources/fxml/main-view.fxml
@@ -5,13 +5,14 @@
-
diff --git a/src/main/resources/fxml/root-view.fxml b/src/main/resources/fxml/root-view.fxml
index da8d366..719cfca 100644
--- a/src/main/resources/fxml/root-view.fxml
+++ b/src/main/resources/fxml/root-view.fxml
@@ -2,55 +2,60 @@
-
-
+
-
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/language/language.properties b/src/main/resources/language/language.properties
index a39cc99..3d642a1 100644
--- a/src/main/resources/language/language.properties
+++ b/src/main/resources/language/language.properties
@@ -13,6 +13,7 @@ main.control.more=\u66F4\u591A
main.status.label-running=\u8FD0\u884C\u4E2D
main.status.label-stop=\u5DF2\u505C\u6B62
main.more.browser=\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00
+main.more.open-config=\u6253\u5F00\u914D\u7F6E\u6587\u4EF6
main.more.open-log=\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6\u5939
alist.status.start.running=AList \u6B63\u5728\u8FD0\u884C\u3002\u3002\u3002
alist.status.start=\u6B63\u5728\u542F\u52A8 AList
@@ -37,10 +38,16 @@ about.app.version=GUI \u7248\u672C
about.alist.update=\u68C0\u67E5 AList \u7248\u672C
about.app.update=\u68C0\u67E5 GUI \u7248\u672C
setup.theme=\u4E3B\u9898
-update.current=\u5F53\u524D
-update.remote=\u6700\u65B0
+update.current=\u5F53\u524D\u7248\u672C
+update.remote=\u6700\u65B0\u7248\u672C
update.upgrade.not=\u5DF2\u662F\u6700\u65B0\u7248\u672C
update.upgrade.new=\u68C0\u67E5\u5230\u65B0\u7248\u672C
+msg.alist.download.notfile=\u6CA1\u68C0\u6D4B\u5230AList\u6587\u4EF6\uFF0C\u662F\u5426\u73B0\u5728\u4E0B\u8F7D\uFF1F
+msg.alist.pwd.copy=\u590D\u5236\u6210\u529F
+admin.pwd.title=\u7BA1\u7406\u5458\u5BC6\u7801
+admin.pwd.toptip=\u65B0\u5BC6\u7801\u53EA\u4F1A\u663E\u793A\u4E00\u6B21
+admin.pwd.user-field=\u7528\u6237\uFF1A
+admin.pwd.pwd-field=\u5BC6\u7801\uFF1A
diff --git a/src/main/resources/language/language_en.properties b/src/main/resources/language/language_en.properties
index 934df88..c92ea92 100644
--- a/src/main/resources/language/language_en.properties
+++ b/src/main/resources/language/language_en.properties
@@ -13,6 +13,7 @@ main.control.more=More
main.status.label-running=Running
main.status.label-stop=Stoped
main.more.browser=Open in browser
+main.more.open-config=Open configuration file
main.more.open-log=Open the log folder
alist.status.start=Starting AList
alist.status.start.running=AList is running...
@@ -37,9 +38,15 @@ about.app.version=GUI Version
about.alist.update=Check AList Version
about.app.update=Check GUI Version
setup.theme=Theme
-update.current=Current
-update.remote=Latest
+update.current=Current Version
+update.remote=Latest Version
update.upgrade.not=It is already the latest version
update.upgrade.new=Detected a new version
+msg.alist.download.notfile=AList file not detected, download now?
+msg.alist.pwd.copy=Copy successful
+admin.pwd.title=Admin Password
+admin.pwd.toptip=The new password will only be displayed once
+admin.pwd.user-field=User:
+admin.pwd.pwd-field=Password :
diff --git a/src/main/resources/language/language_zh_CN.properties b/src/main/resources/language/language_zh_CN.properties
index 46edc4e..6d81384 100644
--- a/src/main/resources/language/language_zh_CN.properties
+++ b/src/main/resources/language/language_zh_CN.properties
@@ -13,6 +13,7 @@ main.control.more=\u66F4\u591A
main.status.label-running=\u8FD0\u884C\u4E2D
main.status.label-stop=\u5DF2\u505C\u6B62
main.more.browser=\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00
+main.more.open-config=\u6253\u5F00\u914D\u7F6E\u6587\u4EF6
main.more.open-log=\u6253\u5F00\u65E5\u5FD7\u6587\u4EF6\u5939
alist.status.stop.stopped=AList \u5DF2\u505C\u6B62
alist.status.start=\u6B63\u5728\u542F\u52A8 AList
@@ -37,9 +38,15 @@ about.app.version=GUI \u7248\u672C
about.alist.update=\u68C0\u67E5 AList \u7248\u672C
about.app.update=\u68C0\u67E5 GUI \u7248\u672C
setup.theme=\u4E3B\u9898
-update.current=\u5F53\u524D
-update.remote=\u6700\u65B0
+update.current=\u5F53\u524D\u7248\u672C
+update.remote=\u6700\u65B0\u7248\u672C
update.upgrade.not=\u5DF2\u662F\u6700\u65B0\u7248\u672C
update.upgrade.new=\u68C0\u67E5\u5230\u65B0\u7248\u672C
+msg.alist.download.notfile=\u6CA1\u68C0\u6D4B\u5230AList\u6587\u4EF6\uFF0C\u662F\u5426\u73B0\u5728\u4E0B\u8F7D\uFF1F
+msg.alist.pwd.copy=\u590D\u5236\u6210\u529F
+admin.pwd.title=\u7BA1\u7406\u5458\u5BC6\u7801
+admin.pwd.toptip=\u65B0\u5BC6\u7801\u53EA\u4F1A\u663E\u793A\u4E00\u6B21
+admin.pwd.user-field=\u7528\u6237\uFF1A
+admin.pwd.pwd-field=\u5BC6\u7801\uFF1A