Initial commit

This commit is contained in:
octopus_yan 2024-08-28 06:13:39 +08:00
commit 198c44f404
24 changed files with 2179 additions and 0 deletions

43
.gitignore vendored Normal file
View File

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

203
pom.xml Normal file
View File

@ -0,0 +1,203 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.octopusyan</groupId>
<artifactId>alist-gui</artifactId>
<version>0.1.0</version>
<name>alist-gui</name>
<organization>
<name>octopus_yan</name>
<url>octopus_yan@foxmail.com</url>
</organization>
<inceptionYear>2024</inceptionYear>
<description>alist windows gui</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<junit.version>5.10.0</junit.version>
<javafx.version>17.0.6</javafx.version>
<slf4j.version>2.0.16</slf4j.version>
<logback.version>1.4.14</logback.version>
<fastjson.version>2.0.52</fastjson.version>
<hutool.version>5.8.25</hutool.version>
<common-lang3.version>3.16.0</common-lang3.version>
<common-io.version>2.16.1</common-io.version>
<common-exec.version>1.4.0</common-exec.version>
</properties>
<dependencies>
<!-- javafx -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- slf4j -->
<!-- https://slf4j.org/manual.html -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- common -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${common-lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${common-io.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>${common-exec.version}</version>
</dependency>
<!-- JSON -->
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.kordamp.ikonli/ikonli-javafx -->
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-javafx</artifactId>
<version>12.3.1</version>
</dependency>
<dependency>
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-coreui-pack</artifactId>
<version>12.3.1</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>exe</nonFilteredFileExtension>
<nonFilteredFileExtension>dll</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>cn.octopusyan.alistgui/cn.octopusyan.alistgui.AppLuncher
</mainClass>
<launcher>launcher</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.github.fvarrui</groupId>
<artifactId>javapackager</artifactId>
<version>1.7.5</version>
<configuration>
<bundleJre>true</bundleJre>
<mainClass>cn.octopusyan.alistgui.AppLuncher</mainClass>
<generateInstaller>false</generateInstaller>
</configuration>
<executions>
<execution>
<id>bundling-for-windows</id>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<platform>windows</platform>
<createZipball>true</createZipball>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

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

View File

@ -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<P extends Pane> 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<P> 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<? extends BaseController<?>> 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<Window>() {
@Override
public void changed(ObservableValue<? extends Window> 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();
/**
* 获取根布局
* <p> 搭配 <code>FxmlUtil.load</code> 使用
*
* @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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String> 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<String> 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<String> 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();
}
}

View File

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

View File

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

View File

@ -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 extends Dialog> {
T alert;
public Builder(T alert) {
this.alert = alert;
if (mOwner != null) this.alert.initOwner(mOwner);
}
public Builder<T> title(String title) {
alert.setTitle(title);
return this;
}
public Builder<T> header(String header) {
alert.setHeaderText(header);
return this;
}
public Builder<T> content(String content) {
alert.setContentText(content);
return this;
}
public Builder<T> icon(String path) {
icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString()));
return this;
}
public Builder<T> 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<ButtonType> result = alert.showAndWait();
listener.onClicked(result.get().getText());
}
/**
* AlertUtil.confirm
*/
public void show(OnChoseListener listener) {
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK) {
listener.confirm();
} else {
listener.cancelOrClose(result.get());
}
}
/**
* AlertUtil.input
* 如果用户点击了取消按钮,将会返回null
*/
public String getInput() {
Optional<String> result = alert.showAndWait();
if (result.isPresent()) {
return result.get();
}
return null;
}
/**
* AlertUtil.choices
*/
public <R> R getChoice(R... choices) {
Optional result = alert.showAndWait();
return (R) result.get();
}
private Stage getStage() {
return (Stage) alert.getDialogPane().getScene().getWindow();
}
}
public static Builder<Alert> info(String content) {
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION)).content(content).header(null);
}
public static Builder<Alert> info() {
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION));
}
public static Builder<Alert> error(String message) {
return new Builder<Alert>(new Alert(Alert.AlertType.ERROR)).header(null).content(message);
}
public static Builder<Alert> warning() {
return new Builder<Alert>(new Alert(Alert.AlertType.WARNING));
}
public static Builder<Alert> exception(Exception ex) {
return new Builder<Alert>(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<Alert> confirm() {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("确认对话框");
return new Builder<Alert>(alert);
}
/**
* 自定义确认对话框 <p>
* <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
*/
public static Builder<Alert> confirm(String... buttons) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
List<ButtonType> 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>(alert);
}
public static Builder<TextInputDialog> input(String content) {
TextInputDialog dialog = new TextInputDialog();
dialog.setContentText(content);
return new Builder<TextInputDialog>(dialog);
}
@SafeVarargs
public static <T> Builder<ChoiceDialog<T>> choices(String hintText, T... choices) {
ChoiceDialog<T> dialog = new ChoiceDialog<T>(choices[0], choices);
dialog.setContentText(hintText);
return new Builder<ChoiceDialog<T>>(dialog);
}
public interface OnChoseListener {
void confirm();
void cancelOrClose(ButtonType buttonType);
}
public interface OnClickListener {
void onClicked(String result);
}
}

View File

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

View File

@ -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<String> listFileNames(String path) {
Collection<File> 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;
}
}

View File

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

View File

@ -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() {
}
}

View File

@ -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 <a href="https://blog.csdn.net/liubin5620/article/details/104618950">配置文件信息读取工具类PropertiesUtils</a>
*/
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;
}
}

View File

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

View File

@ -0,0 +1,3 @@
app.name=${project.name}
app.title=alist gui
app.version=${project.version}

View File

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

View File

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

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.kordamp.ikonli.javafx.*?>
<VBox fx:id="rootPane" styleClass="rootPane" alignment="TOP_CENTER" prefHeight="620.0" prefWidth="700.0" spacing="10.0"
stylesheets="@../css/root-view.css" xmlns="http://javafx.com/javafx/11.0.14-internal"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="cn.octopusyan.alistgui.controller.MainController">
<HBox fx:id="windowHeader" alignment="CENTER_RIGHT" prefWidth="Infinity" spacing="10.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Button fx:id="alwaysOnTopIcon" styleClass="iconButton"/>
<Button fx:id="minimizeIcon" styleClass="iconButton"/>
<Button fx:id="closeIcon" styleClass="iconButton"/>
</HBox>
<TabPane fx:id="tabPane" prefWidth="Infinity" VBox.vgrow="ALWAYS" tabClosingPolicy="UNAVAILABLE">
<padding>
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0"/>
</padding>
<Tab text="主页">
<graphic>
<FontIcon iconLiteral="cil-library" iconColor="white"/>
</graphic>
<VBox>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<HBox styleClass="mainViewHeader" prefWidth="Infinity" alignment="TOP_CENTER">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<Label fx:id="homeLabel" text="AList GUI" alignment="CENTER"/>
<Label fx:id="statusLabel" text="运行中" alignment="TOP_CENTER">
<HBox.margin>
<Insets left="-10.0" top="-5"/>
</HBox.margin>
</Label>
</HBox>
<HBox prefWidth="Infinity" alignment="TOP_CENTER" spacing="25.0">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<VBox.margin>
<Insets bottom="10.0" top="10.0"/>
</VBox.margin>
<Button fx:id="startButton" styleClass="controlMenu" text="开始"/>
<Button fx:id="passwordButton" styleClass="controlMenu" text="密码"/>
<Button fx:id="restartButton" styleClass="controlMenu" text="重启"/>
<Button fx:id="moreButton" styleClass="controlMenu" text="更多"/>
</HBox>
<TextArea fx:id="logArea" editable="false" wrapText="true" prefWidth="Infinity" VBox.vgrow="ALWAYS"
text="123d1a32s1d3as21d3a2s1d3a2s1d3a2s1d3a2s1d3a2s1d3a2s1d32aasda3s21da32s1d32a1sd">
<VBox.margin>
<Insets bottom="10.0" top="10.0"/>
</VBox.margin>
</TextArea>
<HBox prefWidth="Infinity" alignment="CENTER" spacing="25.0">
<padding>
<Insets bottom="10.0" top="30.0"/>
</padding>
<Button fx:id="docmentLabel" text="文档" textAlignment="CENTER">
<graphic>
<FontIcon iconLiteral="cib-readme"/>
</graphic>
</Button>
<Button fx:id="gethubLabel" text="Github" textAlignment="CENTER">
<graphic>
<FontIcon iconLiteral="cib-github"/>
</graphic>
</Button>
<Button fx:id="otherLabel" text="赞助" textAlignment="CENTER">
<graphic>
<FontIcon iconLiteral="cib-buy-me-a-coffee"/>
</graphic>
</Button>
</HBox>
</VBox>
</Tab>
<Tab fx:id="setupTab" text="设置">
<graphic>
<FontIcon iconLiteral="cil-settings" iconColor="white"/>
</graphic>
</Tab>
</TabPane>
</VBox>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="logback.logdir" value="log"/>
<property name="CHARSET" value="utf-8"/>
<property name="logback.app" value="alist-gui"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="%highlight(%d{HH:mm:ss.SSS}) ${logback.app} %boldYellow([%thread]) %highlight(%-5level) %cyan(%logger{36}) - %mdc{client} [%X{trace_id}] %msg%n"/>
<!--输出到控制台 ConsoleAppender-->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>${CHARSET}</charset>
</encoder>
</appender>
<!--输出到文件 fileLog-->
<appender name="fileLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的ThresholdFilter-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"/>
<File>${logback.logdir}/${logback.app}.info.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.info.log</FileNamePattern>
<!--只保留最近30天的日志-->
<maxHistory>30</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>${CHARSET}</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
</encoder>
<!--只打印错误日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="fileLog-debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的ThresholdFilter-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"/>
<File>${logback.logdir}/${logback.app}.debug.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.debug.log</FileNamePattern>
<!--只保留最近30天的日志-->
<maxHistory>30</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<!--日志输出编码格式化-->
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
</encoder>
<!--只打印错误日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 错误日志 -->
<appender name="fileLog-err" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${logback.logdir}/${logback.app}.err.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.err.log</FileNamePattern>
<maxHistory>7</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
</encoder>
<!--只打印错误日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--指定最基础的日志输出级别-->
<root level="INFO">
<!--appender将会添加到这个logger-->
<appender-ref ref="consoleLog"/>
<appender-ref ref="fileLog"/>
<appender-ref ref="fileLog-debug"/>
<appender-ref ref="fileLog-err"/>
</root>
</configuration>