commit 198c44f4040982a597d0d657b7540059f98b6d69 Author: octopus_yan Date: Wed Aug 28 06:13:39 2024 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbdb911 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +.mvn/ +mvnw +mvnw.cmd +log/ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/ +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b06d20a --- /dev/null +++ b/pom.xml @@ -0,0 +1,203 @@ + + + 4.0.0 + + cn.octopusyan + alist-gui + 0.1.0 + alist-gui + + + octopus_yan + octopus_yan@foxmail.com + + + 2024 + alist windows gui + + + 17 + 17 + 17 + UTF-8 + UTF-8 + + 5.10.0 + 17.0.6 + 2.0.16 + 1.4.14 + 2.0.52 + 5.8.25 + 3.16.0 + 2.16.1 + 1.4.0 + + + + + + org.openjfx + javafx-controls + ${javafx.version} + + + org.openjfx + javafx-fxml + ${javafx.version} + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + ch.qos.logback + logback-core + ${logback.version} + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + + + + org.apache.commons + commons-lang3 + ${common-lang3.version} + + + + commons-io + commons-io + ${common-io.version} + + + + org.apache.commons + commons-exec + ${common-exec.version} + + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + + org.kordamp.ikonli + ikonli-javafx + 12.3.1 + + + org.kordamp.ikonli + ikonli-coreui-pack + 12.3.1 + + + + + + + + src/main/resources + true + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + + exe + dll + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + + + default-cli + + cn.octopusyan.alistgui/cn.octopusyan.alistgui.AppLuncher + + launcher + app + app + true + true + true + + + + + + + io.github.fvarrui + javapackager + 1.7.5 + + true + cn.octopusyan.alistgui.AppLuncher + false + + + + bundling-for-windows + package + + package + + + windows + true + + + + + + + \ No newline at end of file diff --git a/src/main/java/cn/octopusyan/alistgui/AppLuncher.java b/src/main/java/cn/octopusyan/alistgui/AppLuncher.java new file mode 100644 index 0000000..c286f84 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/AppLuncher.java @@ -0,0 +1,13 @@ +package cn.octopusyan.alistgui; + +/** + * 启动类 + * + * @author octopus_yan@foxmail.com + */ +public class AppLuncher { + + public static void main(String[] args) { + Application.launch(Application.class, args); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/Application.java b/src/main/java/cn/octopusyan/alistgui/Application.java new file mode 100644 index 0000000..1be5558 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/Application.java @@ -0,0 +1,93 @@ +package cn.octopusyan.alistgui; + +import cn.octopusyan.alistgui.config.AppConstant; +import cn.octopusyan.alistgui.config.CustomConfig; +import cn.octopusyan.alistgui.controller.MainController; +import cn.octopusyan.alistgui.manager.http.HttpConfig; +import cn.octopusyan.alistgui.manager.http.HttpUtil; +import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager; +import cn.octopusyan.alistgui.util.AlertUtil; +import cn.octopusyan.alistgui.util.FxmlUtil; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.util.Objects; + +public class Application extends javafx.application.Application { + private static final Logger logger = LoggerFactory.getLogger(Application.class); + + @Override + public void init() throws Exception { + logger.info("application init ..."); + // 初始化客户端配置 + CustomConfig.init(); + } + + @Override + public void start(Stage primaryStage) throws IOException { + + logger.info("application start ..."); + + // 初始化弹窗工具 + AlertUtil.initOwner(primaryStage); + + // http请求工具初始化 + HttpConfig httpConfig = new HttpConfig(); + if (CustomConfig.hasProxy()) { + InetSocketAddress unresolved = InetSocketAddress.createUnresolved(CustomConfig.proxyHost(), CustomConfig.proxyPort()); + httpConfig.setProxySelector(ProxySelector.of(unresolved)); + } + httpConfig.setConnectTimeout(10); + HttpUtil.init(httpConfig); + + // 全局异常处理 + Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog); + Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog); + + // 启动主界面 + try { + FXMLLoader loader = FxmlUtil.load("root-view"); + loader.setControllerFactory(c -> new MainController(primaryStage)); + Parent root = loader.load();//底层面板 + + Scene scene = new Scene(root); + scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm()); + scene.setFill(Color.TRANSPARENT); + + primaryStage.setScene(scene); + primaryStage.initStyle(StageStyle.TRANSPARENT); + primaryStage.setTitle(String.format("%s v%s", AppConstant.APP_TITLE, AppConstant.APP_VERSION)); + primaryStage.show(); + + MainController controller = loader.getController(); + controller.setApplication(this); + } catch (Throwable t) { + showErrorDialog(Thread.currentThread(), t); + } + + logger.info("application start over ..."); + } + + private void showErrorDialog(Thread t, Throwable e) { + logger.error("", e); + AlertUtil.exceptionAlert(new Exception(e)).show(); + } + + @Override + public void stop() throws Exception { + logger.info("application stop ..."); + // 停止所有线程 + ThreadPoolManager.getInstance().shutdown(); + // 保存应用数据 + CustomConfig.store(); + } +} \ No newline at end of file diff --git a/src/main/java/cn/octopusyan/alistgui/base/BaseController.java b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java new file mode 100644 index 0000000..4237148 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java @@ -0,0 +1,203 @@ +package cn.octopusyan.alistgui.base; + +import cn.octopusyan.alistgui.config.AppConstant; +import cn.octopusyan.alistgui.util.FxmlUtil; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.layout.Pane; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.Window; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; +import java.util.Objects; +import java.util.ResourceBundle; + +/** + * 通用视图控制器基类 + * + * @author octopus_yan@foxmail.com + */ +public abstract class BaseController

implements Initializable { + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + private Application application; + private final Stage primaryStage; + + private double xOffSet = 0, yOffSet = 0; + + protected BaseController(Stage primaryStage) { + this.primaryStage = primaryStage; + } + + public void jumpTo(BaseController

controller) throws IOException { + FXMLLoader fxmlLoader = FxmlUtil.load(controller.getRootFxml()); + + Scene scene = getRootPanel().getScene(); + double oldHeight = getRootPanel().getPrefHeight(); + double oldWidth = getRootPanel().getPrefWidth(); + + Pane root = fxmlLoader.load(); + Stage stage = (Stage) scene.getWindow(); + // 窗口大小 + double newWidth = root.getPrefWidth(); + double newHeight = root.getPrefHeight(); + // 窗口位置 + double newX = stage.getX() - (newWidth - oldWidth) / 2; + double newY = stage.getY() - (newHeight - oldHeight) / 2; + scene.setRoot(root); + stage.setX(newX < 0 ? 0 : newX); + stage.setY(newY < 0 ? 0 : newY); + stage.setWidth(newWidth); + stage.setHeight(newHeight); + + controller = fxmlLoader.getController(); + controller.setApplication(getApplication()); + } + + protected void open(Class> clazz, String title) { + try { + FXMLLoader load = FxmlUtil.load(clazz.getDeclaredConstructor().newInstance().getRootFxml()); + Parent root = load.load(); + Scene scene = new Scene(root); + scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm()); + Stage stage = new Stage(); + stage.setScene(scene); + stage.setTitle(title); + stage.initOwner(getWindow()); + stage.initModality(Modality.WINDOW_MODAL); + stage.show(); + load.getController(); + } catch (Exception e) { + logger.error("", e); + } + } + + @Override + public void initialize(URL url, ResourceBundle resourceBundle) { + // 全局窗口拖拽 + if (dragWindow()) { + // 窗口拖拽 + getRootPanel().setOnMousePressed(event -> { + xOffSet = event.getSceneX(); + yOffSet = event.getSceneY(); + }); + getRootPanel().setOnMouseDragged(event -> { + Stage stage = (Stage) getWindow(); + stage.setX(event.getScreenX() - xOffSet); + stage.setY(event.getScreenY() - yOffSet); + }); + } + + // 窗口初始化完成监听 + getRootPanel().sceneProperty().addListener((observable, oldValue, newValue) -> { + newValue.windowProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Window oldValue, Window newValue) { + //关闭窗口监听 + getWindow().setOnCloseRequest(windowEvent -> onDestroy()); + + // app 版本信息 + if (getAppVersionLabel() != null) getAppVersionLabel().setText("v" + AppConstant.APP_VERSION); + + // 初始化数据 + initData(); + + // 初始化视图样式 + initViewStyle(); + + // 初始化视图事件 + initViewAction(); + } + }); + }); + } + + public void setApplication(Application application) { + this.application = application; + } + + public Application getApplication() { + return application; + } + + public Stage getPrimaryStage() { + return primaryStage; + } + + /** + * 窗口拖拽设置 + * + * @return 是否启用 + */ + public abstract boolean dragWindow(); + + /** + * 获取根布局 + * + * @return 根布局对象 + */ + public abstract P getRootPanel(); + + /** + * 获取根布局 + *

搭配 FxmlUtil.load 使用 + * + * @return 根布局对象 + * @see FxmlUtil#load(String) + */ + protected String getRootFxml() { + System.out.println(getClass().getSimpleName()); + return ""; + } + + protected Window getWindow() { + return getRootPanel().getScene().getWindow(); + } + + /** + * App版本信息标签 + */ + public Label getAppVersionLabel() { + return null; + } + + /** + * 初始化数据 + */ + public abstract void initData(); + + /** + * 视图样式 + */ + public abstract void initViewStyle(); + + /** + * 视图事件 + */ + public abstract void initViewAction(); + + /** + * 关闭窗口 + */ + public void onDestroy() { + Stage stage = (Stage) getWindow(); + stage.hide(); + stage.close(); + try { + Thread.sleep(1000); + Platform.exit(); + } catch (InterruptedException e) { + logger.error("", e); + } + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java b/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java new file mode 100644 index 0000000..291a381 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java @@ -0,0 +1,21 @@ +package cn.octopusyan.alistgui.config; + +import cn.octopusyan.alistgui.util.PropertiesUtils; +import org.apache.commons.io.FileUtils; + +import java.io.File; + +/** + * 应用信息 + * + * @author octopus_yan@foxmail.com + */ +public class AppConstant { + public static final String APP_TITLE = PropertiesUtils.getInstance().getProperty("app.title"); + public static final String APP_NAME = PropertiesUtils.getInstance().getProperty("app.name"); + public static final String APP_VERSION = PropertiesUtils.getInstance().getProperty("app.version"); + public static final String DATA_DIR_PATH = System.getProperty("user.home") + File.separator + "AppData" + File.separator + "Local" + File.separator + APP_NAME; + public static final String TMP_DIR_PATH = FileUtils.getTempDirectoryPath() + APP_NAME; + public static final String CUSTOM_CONFIG_PATH = DATA_DIR_PATH + File.separator + "config.properties"; + public static final String BAK_FILE_PATH = AppConstant.TMP_DIR_PATH + File.separator + "bak"; +} diff --git a/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java b/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java new file mode 100644 index 0000000..dc2de54 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java @@ -0,0 +1,110 @@ +package cn.octopusyan.alistgui.config; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.Properties; + +/** + * 客户端设置 + * + * @author octopus_yan@foxmail.com + */ +public class CustomConfig { + private static final Logger logger = LoggerFactory.getLogger(CustomConfig.class); + private static final Properties properties = new Properties(); + public static final String PROXY_HOST_KEY = "proxy.host"; + public static final String PROXY_PORT_KEY = "proxy.port"; + + public static void init() { + FileReader reader = null; + try { + File file = new File(AppConstant.CUSTOM_CONFIG_PATH); + if (!file.exists()) { + // 创建配置文件 + if (!file.getParentFile().exists()) { + FileUtils.createParentDirectories(file); + } + boolean newFile = file.createNewFile(); + // 保存配置 + store(); + } else { + reader = new FileReader(file); + properties.load(reader); + } + } catch (Exception e) { + logger.error("读取配置文件失败", e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + logger.error("关闭配置文件流", e); + } + } + } + + /** + * 是否配置代理 + */ + public static boolean hasProxy() { + String host = proxyHost(); + Integer port = proxyPort(); + + return StringUtils.isNoneBlank(host) && Objects.nonNull(port); + } + + /** + * 代理地址 + */ + public static String proxyHost() { + return properties.getProperty(PROXY_HOST_KEY); + } + + /** + * 代理地址 + */ + public static void proxyHost(String host) { + properties.setProperty(PROXY_HOST_KEY, host); + } + + /** + * 代理端口 + */ + public static Integer proxyPort() { + try { + return Integer.parseInt(properties.getProperty(PROXY_PORT_KEY)); + } catch (Exception ignored) { + } + return 10809; + } + + /** + * 代理端口 + */ + public static void proxyPort(int port) { + properties.setProperty(PROXY_PORT_KEY, String.valueOf(port)); + } + + + /** + * 保存配置 + */ + public static void store() { + // 生成配置文件 + try { + properties.store(new PrintStream(AppConstant.CUSTOM_CONFIG_PATH), String.valueOf(StandardCharsets.UTF_8)); + } catch (IOException e) { + logger.error("保存客户端配置失败", e); + } + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/controller/MainController.java b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java new file mode 100644 index 0000000..34f5e20 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java @@ -0,0 +1,102 @@ +package cn.octopusyan.alistgui.controller; + +import cn.octopusyan.alistgui.base.BaseController; +import javafx.css.PseudoClass; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.TabPane; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * 主页面控制器 + * + * @author octopus_yan@foxmail.com + */ +public class MainController extends BaseController implements Initializable { + + private double xOffset; + private double yOffset; + + // 布局 + @FXML + public VBox rootPane; + @FXML + public HBox windowHeader; + @FXML + public Button alwaysOnTopIcon; + @FXML + public Button minimizeIcon; + @FXML + public Button closeIcon; + + // 界面 + @FXML + public TabPane tabPane; + + public MainController(Stage primaryStage) { + super(primaryStage); + } + + /** + * 窗口拖拽设置 + * + * @return 是否启用 + */ + @Override + public boolean dragWindow() { + return false; + } + + /** + * 获取根布局 + * + * @return 根布局对象 + */ + @Override + public VBox getRootPanel() { + return rootPane; + } + + /** + * 初始化数据 + */ + @Override + public void initData() { + + } + + /** + * 视图样式 + */ + @Override + public void initViewStyle() { + + } + + /** + * 视图事件 + */ + @Override + public void initViewAction() { + closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> onDestroy()); + minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> ((Stage) rootPane.getScene().getWindow()).setIconified(true)); + alwaysOnTopIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> { + boolean newVal = !getPrimaryStage().isAlwaysOnTop(); + alwaysOnTopIcon.pseudoClassStateChanged(PseudoClass.getPseudoClass("always-on-top"), newVal); + getPrimaryStage().setAlwaysOnTop(newVal); + }); + + windowHeader.setOnMousePressed(event -> { + xOffset = getPrimaryStage().getX() - event.getScreenX(); + yOffset = getPrimaryStage().getY() - event.getScreenY(); + }); + windowHeader.setOnMouseDragged(event -> { + getPrimaryStage().setX(event.getScreenX() + xOffset); + getPrimaryStage().setY(event.getScreenY() + yOffset); + }); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java new file mode 100644 index 0000000..fb9ad1a --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java @@ -0,0 +1,183 @@ +package cn.octopusyan.alistgui.manager.http; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.net.Authenticator; +import java.net.CookieHandler; +import java.net.ProxySelector; +import java.net.http.HttpClient; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.concurrent.Executor; + +/** + * Http配置参数 + * + * @author octopus_yan@foxmail.com + */ +public class HttpConfig { + private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class); + /** + * http版本 + */ + private HttpClient.Version version = HttpClient.Version.HTTP_2; + + /** + * 转发策略 + */ + private HttpClient.Redirect redirect = HttpClient.Redirect.NORMAL; + + /** + * 线程池 + */ + private Executor executor; + + /** + * 认证 + */ + private Authenticator authenticator; + + /** + * 代理 + */ + private ProxySelector proxySelector; + + /** + * CookieHandler + */ + private CookieHandler cookieHandler; + + /** + * sslContext + */ + private SSLContext sslContext; + + /** + * sslParams + */ + private SSLParameters sslParameters; + + /** + * 连接超时时间毫秒 + */ + private int connectTimeout = 10000; + + /** + * 默认读取数据超时时间 + */ + private int defaultReadTimeout = 1200000; + + + public HttpConfig() { + TrustManager[] trustAllCertificates = new X509TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; // Not relevant. + } + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + // TODO Auto-generated method stub + } + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + // TODO Auto-generated method stub + } + }}; + sslParameters = new SSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm(""); + + + try { + sslContext = SSLContext.getInstance("TLSv1.2"); + System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证 + sslContext.init(null, trustAllCertificates, new SecureRandom()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + logger.error("", e); + } + + } + + + public HttpClient.Version getVersion() { + return version; + } + + public void setVersion(HttpClient.Version version) { + this.version = version; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + + public HttpClient.Redirect getRedirect() { + return redirect; + } + + public void setRedirect(HttpClient.Redirect redirect) { + this.redirect = redirect; + } + + public Executor getExecutor() { + return executor; + } + + public void setExecutor(Executor executor) { + this.executor = executor; + } + + public Authenticator getAuthenticator() { + return authenticator; + } + + public void setAuthenticator(Authenticator authenticator) { + this.authenticator = authenticator; + } + + public ProxySelector getProxySelector() { + return proxySelector; + } + + public void setProxySelector(ProxySelector proxySelector) { + this.proxySelector = proxySelector; + } + + public CookieHandler getCookieHandler() { + return cookieHandler; + } + + public void setCookieHandler(CookieHandler cookieHandler) { + this.cookieHandler = cookieHandler; + } + + public int getDefaultReadTimeout() { + return defaultReadTimeout; + } + + public void setDefaultReadTimeout(int defaultReadTimeout) { + this.defaultReadTimeout = defaultReadTimeout; + } + + public SSLContext getSslContext() { + return sslContext; + } + + public SSLParameters getSslParameters() { + return sslParameters; + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java new file mode 100644 index 0000000..a351a33 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java @@ -0,0 +1,141 @@ +package cn.octopusyan.alistgui.manager.http; + +import com.alibaba.fastjson2.JSONObject; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +/** + * 网络请求封装 + * + * @author octopus_yan@foxmail.com + */ +public class HttpUtil { + private volatile static HttpUtil util; + private volatile HttpClient httpClient; + private final HttpConfig httpConfig; + + private HttpUtil(HttpConfig httpConfig) { + this.httpConfig = httpConfig; + this.httpClient = createClient(httpConfig); + } + + public static HttpUtil getInstance() { + if (util == null) { + throw new RuntimeException("are you ready ?"); + } + return util; + } + + public static void init(HttpConfig httpConfig) { + synchronized (HttpUtil.class) { + util = new HttpUtil(httpConfig); + } + } + + private static HttpClient createClient(HttpConfig httpConfig) { + HttpClient.Builder builder = HttpClient.newBuilder() + .version(httpConfig.getVersion()) + .connectTimeout(Duration.ofMillis(httpConfig.getConnectTimeout())) + .sslContext(httpConfig.getSslContext()) + .sslParameters(httpConfig.getSslParameters()) + .followRedirects(httpConfig.getRedirect()); + Optional.ofNullable(httpConfig.getAuthenticator()).ifPresent(builder::authenticator); + Optional.ofNullable(httpConfig.getCookieHandler()).ifPresent(builder::cookieHandler); + Optional.ofNullable(httpConfig.getProxySelector()).ifPresent(builder::proxy); + Optional.ofNullable(httpConfig.getExecutor()).ifPresent(builder::executor); + return builder.build(); + } + + public HttpUtil proxy(String host, int port) { + if (httpClient == null) + throw new RuntimeException("are you ready ?"); + + InetSocketAddress unresolved = InetSocketAddress.createUnresolved(host, port); + ProxySelector other = ProxySelector.of(unresolved); + this.httpConfig.setProxySelector(other); + this.httpClient = createClient(httpConfig); + return this; + } + + public void clearProxy() { + if (httpClient == null) + throw new RuntimeException("are you ready ?"); + + httpConfig.setProxySelector(HttpClient.Builder.NO_PROXY); + httpClient = createClient(httpConfig); + } + + public HttpClient getHttpClient() { + return httpClient; + } + + public String get(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException { + HttpRequest.Builder request = getRequest(uri + createFormParams(param), header).GET(); + HttpResponse response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + return response.body(); + } + + public String post(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException { + HttpRequest.Builder request = getRequest(uri, header) + .POST(HttpRequest.BodyPublishers.ofString(param.toJSONString())); + HttpResponse response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + return response.body(); + } + + public String postForm(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException { + HttpRequest.Builder request = getRequest(uri + createFormParams(param), header) + .POST(HttpRequest.BodyPublishers.noBody()); + + HttpResponse response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + return response.body(); + } + + private HttpRequest.Builder getRequest(String uri, JSONObject header) { + HttpRequest.Builder request = HttpRequest.newBuilder(); + // 请求地址 + request.uri(URI.create(uri)); + // 请求头 + if (header != null && !header.isEmpty()) { + for (String key : header.keySet()) { + request.header(key, header.getString(key)); + } + } + return request; + } + + private String createFormParams(JSONObject params) { + StringBuilder formParams = new StringBuilder(); + if (params == null) { + return formParams.toString(); + } + for (String key : params.keySet()) { + Object value = params.get(key); + if (value instanceof String) { + value = URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8); + formParams.append("&").append(key).append("=").append(value); + } else if (value instanceof Number) { + formParams.append("&").append(key).append("=").append(value); + } else if (value instanceof List) { + formParams.append("&").append(key).append("=").append(params.getJSONArray(key)); + } else { + formParams.append("&").append(key).append("=").append(params.getJSONObject(key)); + } + } + if (!formParams.isEmpty()) { + formParams = new StringBuilder("?" + formParams.substring(1)); + } + + return formParams.toString(); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/manager/thread/ThreadFactory.java b/src/main/java/cn/octopusyan/alistgui/manager/thread/ThreadFactory.java new file mode 100644 index 0000000..97e5811 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/manager/thread/ThreadFactory.java @@ -0,0 +1,49 @@ +package cn.octopusyan.alistgui.manager.thread; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 自定义线程工厂 + * + * @author octopus_yan@foxmail.com + */ +public class ThreadFactory implements java.util.concurrent.ThreadFactory { + private static final Logger logger = LoggerFactory.getLogger(ThreadFactory.class); + + public static final String DEFAULT_THREAD_PREFIX = "thread-factory-pool"; + + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public ThreadFactory() { + this(DEFAULT_THREAD_PREFIX); + } + + public ThreadFactory(String prefix) { + group = Thread.currentThread().getThreadGroup(); + namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-"; + } + + @Override + public Thread newThread(Runnable runnable) { + + Thread t = new Thread(group, runnable, + namePrefix + threadNumber.getAndIncrement(), + 0); + + t.setUncaughtExceptionHandler((t1, e) -> { + logger.error("thread : {}, error", t1.getName(), e); + }); + + if (t.isDaemon()) + t.setDaemon(false); + if (t.getPriority() != Thread.NORM_PRIORITY) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/manager/thread/ThreadPoolManager.java b/src/main/java/cn/octopusyan/alistgui/manager/thread/ThreadPoolManager.java new file mode 100644 index 0000000..4df21c7 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/manager/thread/ThreadPoolManager.java @@ -0,0 +1,29 @@ +package cn.octopusyan.alistgui.manager.thread; + + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 线程池管理类 + */ +public final class ThreadPoolManager extends ThreadPoolExecutor { + + private static volatile ThreadPoolManager sInstance; + + private ThreadPoolManager() { + super(32, + 200, + 10, + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(200), + new ThreadFactory(ThreadFactory.DEFAULT_THREAD_PREFIX), + new DiscardPolicy()); + } + + public static ThreadPoolManager getInstance() { + if (sInstance == null) sInstance = new ThreadPoolManager(); + return sInstance; + } +} \ No newline at end of file diff --git a/src/main/java/cn/octopusyan/alistgui/util/AlertUtil.java b/src/main/java/cn/octopusyan/alistgui/util/AlertUtil.java new file mode 100644 index 0000000..1ea3f1d --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/util/AlertUtil.java @@ -0,0 +1,228 @@ +package cn.octopusyan.alistgui.util; + +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.stage.Stage; +import javafx.stage.Window; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * 弹窗工具 + * + * @author octopus_yan@foxmail.com + */ +public class AlertUtil { + private static Window mOwner; + private static Builder builder; + + public static void initOwner(Stage stage) { + AlertUtil.mOwner = stage; + } + + public static class Builder { + T alert; + + public Builder(T alert) { + this.alert = alert; + if (mOwner != null) this.alert.initOwner(mOwner); + } + + public Builder title(String title) { + alert.setTitle(title); + return this; + } + + public Builder header(String header) { + alert.setHeaderText(header); + return this; + } + + public Builder content(String content) { + alert.setContentText(content); + return this; + } + + public Builder icon(String path) { + icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString())); + return this; + } + + public Builder icon(Image image) { + getStage().getIcons().add(image); + return this; + } + + public void show() { + if (AlertUtil.builder == null) { + AlertUtil.builder = this; + } else if (AlertUtil.builder.alert.isShowing()) { + if (!Objects.equals(AlertUtil.builder.alert.getContentText(), alert.getContentText())) + ((Alert) AlertUtil.builder.alert).setOnHidden(event -> { + AlertUtil.builder = null; + show(); + }); + } + alert.showAndWait(); + } + + /** + * AlertUtil.confirm + */ + public void show(OnClickListener listener) { + + Optional result = alert.showAndWait(); + + listener.onClicked(result.get().getText()); + } + + /** + * AlertUtil.confirm + */ + public void show(OnChoseListener listener) { + Optional result = alert.showAndWait(); + if (result.get() == ButtonType.OK) { + listener.confirm(); + } else { + listener.cancelOrClose(result.get()); + } + } + + /** + * AlertUtil.input + * 如果用户点击了取消按钮,将会返回null + */ + public String getInput() { + Optional result = alert.showAndWait(); + if (result.isPresent()) { + return result.get(); + } + return null; + } + + /** + * AlertUtil.choices + */ + public R getChoice(R... choices) { + Optional result = alert.showAndWait(); + return (R) result.get(); + } + + private Stage getStage() { + return (Stage) alert.getDialogPane().getScene().getWindow(); + } + } + + public static Builder info(String content) { + return new Builder(new Alert(Alert.AlertType.INFORMATION)).content(content).header(null); + } + + public static Builder info() { + return new Builder(new Alert(Alert.AlertType.INFORMATION)); + } + + public static Builder error(String message) { + return new Builder(new Alert(Alert.AlertType.ERROR)).header(null).content(message); + } + + public static Builder warning() { + return new Builder(new Alert(Alert.AlertType.WARNING)); + } + + public static Builder exception(Exception ex) { + return new Builder(exceptionAlert(ex)); + } + + public static Alert exceptionAlert(Exception ex) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Exception Dialog"); + alert.setHeaderText(ex.getClass().getSimpleName()); + alert.setContentText(ex.getMessage()); + + // 创建可扩展的异常。 + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + String exceptionText = sw.toString(); + + Label label = new Label("The exception stacktrace was :"); + + TextArea textArea = new TextArea(exceptionText); + textArea.setEditable(false); + textArea.setWrapText(true); + + textArea.setMaxWidth(Double.MAX_VALUE); + textArea.setMaxHeight(Double.MAX_VALUE); + GridPane.setVgrow(textArea, Priority.ALWAYS); + GridPane.setHgrow(textArea, Priority.ALWAYS); + + GridPane expContent = new GridPane(); + expContent.setMaxWidth(Double.MAX_VALUE); + expContent.add(label, 0, 0); + expContent.add(textArea, 0, 1); + + // 将可扩展异常设置到对话框窗格中。 + alert.getDialogPane().setExpandableContent(expContent); + return alert; + } + + /** + * 确认对话框 + */ + public static Builder confirm() { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("确认对话框"); + return new Builder(alert); + } + + /** + * 自定义确认对话框

+ * "Cancel" OR "取消" 为取消按钮 + */ + public static Builder confirm(String... buttons) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + + List buttonList = Arrays.stream(buttons).map((type) -> { + ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER; + if ("Cancel".equals(type) || "取消".equals(type)) + buttonData = ButtonBar.ButtonData.CANCEL_CLOSE; + return new ButtonType(type, buttonData); + }).collect(Collectors.toList()); + + alert.getButtonTypes().setAll(buttonList); + + return new Builder(alert); + } + + public static Builder input(String content) { + TextInputDialog dialog = new TextInputDialog(); + dialog.setContentText(content); + return new Builder(dialog); + } + + @SafeVarargs + public static Builder> choices(String hintText, T... choices) { + ChoiceDialog dialog = new ChoiceDialog(choices[0], choices); + dialog.setContentText(hintText); + return new Builder>(dialog); + } + + + public interface OnChoseListener { + void confirm(); + + void cancelOrClose(ButtonType buttonType); + } + + public interface OnClickListener { + void onClicked(String result); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java b/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java new file mode 100644 index 0000000..03efe0a --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java @@ -0,0 +1,28 @@ +package cn.octopusyan.alistgui.util; + +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; + +/** + * 剪切板工具 + * + * @author octopus_yan@foxmail.com + */ +public class ClipUtil { + //获取系统剪切板 + private static final Clipboard clipboard = Clipboard.getSystemClipboard(); + + public static void setClip(String data) { + clipboard.clear(); + // 设置剪切板内容 + ClipboardContent clipboardContent = new ClipboardContent(); + clipboardContent.putString(data); + clipboard.setContent(clipboardContent); + + } + + public static String getString() { + // javafx 从剪切板获取文本 + return clipboard.getString(); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java b/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java new file mode 100644 index 0000000..1cc4ba3 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java @@ -0,0 +1,135 @@ +package cn.octopusyan.alistgui.util; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.CanReadFileFilter; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 文件工具类 + * + * @author octopus_yan@foxmail.com + */ +public class FileUtil { + private static final Logger logger = LoggerFactory.getLogger(FileUtil.class); + + public static File[] ls(String path) { + File dir = new File(path); + if (!dir.exists()) + throw new RuntimeException(path + "不存在!"); + + if (!dir.isDirectory()) + throw new RuntimeException(path + "不是一个文件夹!"); + + return dir.listFiles(); + } + + public static void copyFilesFromDir(String path, String dest) throws IOException { + if (StringUtils.isBlank(path) || StringUtils.isBlank(dest)) { + logger.error("path is blank !"); + return; + } + File dir = new File(path); + if (!dir.exists()) { + logger.error("[" + path + "] 不存在!"); + return; + } + if (!dir.isDirectory()) { + logger.error("[" + path + "] 不是一个文件夹!"); + } + + File[] files = dir.listFiles(); + if (files == null) return; + + File directory = new File(dest); + if (directory.exists() && !directory.isDirectory()) { + logger.error("[" + dest + "] 不是一个文件夹!"); + } + + FileUtils.forceMkdir(directory); + + for (File file : files) { + copyFile(file, new File(dest + File.separator + file.getName())); + } + } + + public static void copyFile(File in, File out) throws IOException { + copyFile(Files.newInputStream(in.toPath()), out); + } + + public static void copyFile(InputStream input, File out) throws IOException { + OutputStream output = null; + try { + output = Files.newOutputStream(out.toPath()); + byte[] buf = new byte[1024]; + int bytesRead; + while ((bytesRead = input.read(buf)) > 0) { + output.write(buf, 0, bytesRead); + } + } catch (IOException e) { + logger.error("", e); + } finally { + if (output != null) input.close(); + if (output != null) output.close(); + } + } + + /** + * 获取文件主名称 + * + * @param file 文件对象 + * @return 文件名称 + */ + public static String mainName(File file) { + //忽略判断 + String fileName = file.getName(); + return fileName.substring(0, fileName.lastIndexOf(".")); + } + + public static List listFileNames(String path) { + Collection files = FileUtils.listFiles(new File(path), CanReadFileFilter.CAN_READ, null); + return files.stream().map(File::getName).collect(Collectors.toList()); + } + + /** + * 返回被查找到的文件的绝对路径(匹配到一个就返回) + * + * @param root 根目录文件 + * @param fileName 要找的文件名 + * @return 绝对路径 + */ + private static String findFiles(File root, String fileName) { + //定义一个返回值 + String path = null; + //如果传进来的是目录,并且存在 + if (root.exists() && root.isDirectory()) { + //遍历文件夹中的各个文件 + File[] files = root.listFiles(); + if (files != null) { + for (File file : files) { + //如果path的值没有变化 + if (path == null) { + if (file.isFile() && file.getName().contains(fileName)) { + path = file.getAbsolutePath(); + } else { + path = findFiles(file, fileName); + } + } else { + break;//跳出循环,增加性能 + } + } + } + } + return path; + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java b/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java new file mode 100644 index 0000000..404d3a6 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java @@ -0,0 +1,26 @@ +package cn.octopusyan.alistgui.util; + +import javafx.fxml.FXMLLoader; +import javafx.fxml.JavaFXBuilderFactory; + +import java.nio.charset.StandardCharsets; + +/** + * FXML 工具 + * + * @author octopus_yan@foxmail.com + */ +public class FxmlUtil { + + public static FXMLLoader load(String name) { + String prefix = "/fxml/"; + String suffix = ".fxml"; + return new FXMLLoader( + FxmlUtil.class.getResource(prefix + name + suffix), + null, + new JavaFXBuilderFactory(), + null, + StandardCharsets.UTF_8 + ); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java new file mode 100644 index 0000000..1aec45b --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java @@ -0,0 +1,96 @@ +package cn.octopusyan.alistgui.util; + +import org.apache.commons.exec.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + * 命令工具类 + * + * @author octopus_yan@foxmail.com + */ +public class ProcessesUtil { + private static final Logger logger = LoggerFactory.getLogger(ProcessesUtil.class); + private static final String NEWLINE = System.lineSeparator(); + private static final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler(); + + public static boolean exec(String command) { + try { + exec(command, new OnExecuteListener() { + @Override + public void onExecute(String msg) { + + } + + @Override + public void onExecuteSuccess(int exitValue) { + + } + + @Override + public void onExecuteError(Exception e) { + + } + + @Override + public void onExecuteOver() { + + } + }); + handler.waitFor(); + } catch (Exception e) { + logger.error("", e); + } + return 0 == handler.getExitValue(); + } + + public static void exec(String command, OnExecuteListener listener) { + LogOutputStream logout = new LogOutputStream() { + @Override + protected void processLine(String line, int logLevel) { + if (listener != null) listener.onExecute(line + NEWLINE); + } + }; + + CommandLine commandLine = CommandLine.parse(command); + DefaultExecutor executor = DefaultExecutor.builder().get(); + executor.setStreamHandler(new PumpStreamHandler(logout, logout)); + DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() { + @Override + public void onProcessComplete(int exitValue) { + if (listener != null) { + listener.onExecuteSuccess(exitValue); + } + } + + @Override + public void onProcessFailed(ExecuteException e) { + if (listener != null) { + listener.onExecuteError(e); + } + } + }; + try { + executor.execute(commandLine, handler); + } catch (IOException e) { + if (listener != null) listener.onExecuteError(e); + } + } + + public interface OnExecuteListener { + void onExecute(String msg); + + void onExecuteSuccess(int exitValue); + + void onExecuteError(Exception e); + void onExecuteOver(); + } + + /** + * Prevent construction. + */ + private ProcessesUtil() { + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/util/PropertiesUtils.java b/src/main/java/cn/octopusyan/alistgui/util/PropertiesUtils.java new file mode 100644 index 0000000..00cddbe --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/util/PropertiesUtils.java @@ -0,0 +1,77 @@ +package cn.octopusyan.alistgui.util; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * 配置文件读取工具 + * + * @author liubin5620 + * @see 配置文件信息读取工具类【PropertiesUtils】 + */ +public class PropertiesUtils { + /** + * 主配置文件 + */ + private final Properties properties; + /** + * 启用配置文件 + */ + private final Properties propertiesCustom; + + private static PropertiesUtils propertiesUtils = new PropertiesUtils(); + + public static final Logger logger = LoggerFactory.getLogger(PropertiesUtils.class); + + /** + * 私有构造,禁止直接创建 + */ + private PropertiesUtils() { + // 读取配置启用的配置文件名 + properties = new Properties(); + propertiesCustom = new Properties(); + InputStream in = PropertiesUtils.class.getClassLoader().getResourceAsStream("application.properties"); + try { + properties.load(in); + // 加载启用的配置 + String property = properties.getProperty("profiles.active"); + if (!StringUtils.isBlank(property)) { + InputStream cin = PropertiesUtils.class.getClassLoader().getResourceAsStream("application-" + property + ".properties"); + propertiesCustom.load(cin); + } + } catch (IOException e) { + logger.error("读取配置文件失败", e); + } + } + + /** + * 获取单例 + * + * @return PropertiesUtils + */ + public static PropertiesUtils getInstance() { + if (propertiesUtils == null) { + propertiesUtils = new PropertiesUtils(); + } + return propertiesUtils; + } + + /** + * 根据属性名读取值 + * 先去主配置查询,如果查询不到,就去启用配置查询 + * + * @param name 名称 + */ + public String getProperty(String name) { + String val = properties.getProperty(name); + if (StringUtils.isBlank(val)) { + val = propertiesCustom.getProperty(name); + } + return val; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..a09a857 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,17 @@ +module cn.octopusyan.alistgui { + requires java.net.http; + requires javafx.controls; + requires javafx.fxml; + requires javafx.graphics; + requires org.apache.commons.io; + requires org.apache.commons.lang3; + requires org.apache.commons.exec; + requires org.slf4j; + requires ch.qos.logback.core; + requires ch.qos.logback.classic; + requires com.alibaba.fastjson2; + + exports cn.octopusyan.alistgui; + opens cn.octopusyan.alistgui to javafx.fxml; + opens cn.octopusyan.alistgui.controller to javafx.fxml; +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..7553f33 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,3 @@ +app.name=${project.name} +app.title=alist gui +app.version=${project.version} \ No newline at end of file diff --git a/src/main/resources/css/root-view.css b/src/main/resources/css/root-view.css new file mode 100644 index 0000000..13fc80f --- /dev/null +++ b/src/main/resources/css/root-view.css @@ -0,0 +1,176 @@ +@import "root.css"; + +/************************************************** + * Window Header +**************************************************/ +#windowHeader .iconButton { + /*-fx-max-height: 5px;*/ + /*-fx-max-width: 5px;*/ + -fx-border-radius: 15; +} + +#rootPane #closeIcon { + -fx-color: #fa6057; + -fx-opacity: 0.5; +} + +#rootPane #closeIcon:hover { + -fx-opacity: 1.0; +} + +#rootPane #minimizeIcon { + -fx-color: #fbbc2e; + -fx-opacity: 0.5; +} + +#rootPane #minimizeIcon:hover { + -fx-opacity: 1.0; +} + +#rootPane #alwaysOnTopIcon { + -fx-color: #27c940; + -fx-opacity: 0.5; +} + +#rootPane #alwaysOnTopIcon:hover { + -fx-opacity: 1.0; +} + +#rootPane #alwaysOnTopIcon:always-on-top { + -fx-opacity: 1.0; +} + +/************************************************** + * Tab label +**************************************************/ + +#tabPane .tab-header-area { + -fx-background-radius: 10; + -fx-background-color: #0000; +} + +#tabPane .headers-region { + -fx-alignment: TOP_CENTER; + -fx-background-color: #18181a; + -fx-background-radius: 10; + -fx-padding: 5 0 5 0; +} + +#tabPane .tab-header-background { + -fx-background-color: #0000; +} + +#tabPane .tab { + -fx-text-fill: white; + -fx-padding: 10 20 10 20; + -fx-background-radius: 10; + -fx-background-color: #0000; + -fx-border-width: 0; +} + +#tabPane .tab-label { + -fx-font-size: 15px; + -fx-text-fill: #707079; +} + +#tabPane .tab:selected { + -fx-background-color: #2c69e0; +} + +#tabPane .tab:selected .tab-label { + -fx-text-fill: white; +} + +/************************************************** + * Main View +**************************************************/ +#homeLabel { + -fx-font-size: 35px; + -fx-font-weight: bold; + -fx-text-fill: white; + -fx-font-family: 'JetBrains Mono'; +} + +#statusLabel { + -fx-padding: 2 5 2 5; + -fx-text-fill: black; + -fx-background-color: #1bc964; + -fx-background-radius: 10; + -fx-text-alignment: CENTER; + -fx-border-radius: 10; + -fx-border-color: black; +} + +.controlMenu { + -fx-font-size: 15; + -fx-background-radius: 10px; + -fx-padding: 10 40 10 40; + -fx-border-radius: 10; +} + +.controlMenu:focused { + -fx-opacity: 0.5; +} + +#startButton { + -fx-background-color: #fa6057; + -fx-text-fill: white; + -fx-opacity: 1.0; +} + +#passwordButton { + -fx-background-color: #1bc964; + -fx-opacity: 1.0; +} + +#restartButton { + -fx-background-color: linear-gradient(#57b4f2, #9198e5); + -fx-text-fill: white; + -fx-opacity: 1.0; +} + +#moreButton { + -fx-background-color: #0000; + -fx-text-fill: #9254d1; + -fx-border-color: #9254d1; + -fx-border-width: 2px; + -fx-opacity: 1.0; +} + +#tabPane .tab:selected .focus-indicator { + -fx-border-width: 0; + -fx-border-color: #0000; +} + +#logArea { + -fx-text-fill: #e3e4e4; + -fx-font-family: 'JetBrains Mono'; + -fx-font-size: 20; + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-color: transparent; + -fx-background-insets: 0; + -fx-background-color: #18181c; +} + +#logArea .content { + -fx-padding: 15; + -fx-background-radius: 15; + -fx-background-color: #18181c; + -fx-border-radius: 15; + -fx-border-color: transparent; +} + +#logArea .scroll-pane { + -fx-background-color: transparent; + } +#logArea .scroll-pane .viewport{ + -fx-background-color: transparent; +} + +#logArea:focused { + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-color: transparent; + -fx-background-color: #18181c; +} \ No newline at end of file diff --git a/src/main/resources/css/root.css b/src/main/resources/css/root.css new file mode 100644 index 0000000..8fa808f --- /dev/null +++ b/src/main/resources/css/root.css @@ -0,0 +1,15 @@ + +/************************************************** + * Root +**************************************************/ + +.rootPane { + -fx-background-color: black; + -fx-background-radius: 10; + -fx-border-radius: 10; +} + +.root { + -fx-font-family: "Comic Sans MS"; +} + diff --git a/src/main/resources/fxml/root-view.fxml b/src/main/resources/fxml/root-view.fxml new file mode 100644 index 0000000..8ff9682 --- /dev/null +++ b/src/main/resources/fxml/root-view.fxml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..8ee2621 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + ${CHARSET} + + + + + + + + ${logback.logdir}/${logback.app}.info.log + + + ${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.info.log + + 30 + + 1GB + + + + ${CHARSET} + %d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n + + + + INFO + ACCEPT + DENY + + + + + + + ${logback.logdir}/${logback.app}.debug.log + + + ${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.debug.log + + 30 + + 1GB + + + + UTF-8 + %d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n + + + + DEBUG + ACCEPT + DENY + + + + + + ${logback.logdir}/${logback.app}.err.log + + ${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.err.log + 7 + 1GB + + + UTF-8 + %d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n + + + + ERROR + ACCEPT + DENY + + + + + + + + + + + + + \ No newline at end of file