feat: alist 检查更次

This commit is contained in:
octopus_yan 2024-09-12 22:02:22 +08:00
parent 80bda287cc
commit cfd7075c8f
22 changed files with 672 additions and 199 deletions

View File

@ -9,9 +9,11 @@ import javafx.concurrent.Task;
*/
public abstract class BaseTask extends Task<Void> {
private final ThreadPoolManager Executor = ThreadPoolManager.getInstance();
protected Listener listener;
@Override
protected Void call() throws Exception {
if (listener != null) listener.onStart();
task();
return null;
}
@ -23,7 +25,8 @@ public abstract class BaseTask extends Task<Void> {
protected abstract void task() throws Exception;
public void onListen(Listener listener) {
if (listener == null) return;
this.listener = listener;
if (this.listener == null) return;
setOnRunning(_ -> listener.onRunning());
setOnCancelled(_ -> listener.onCancelled());
@ -36,6 +39,9 @@ public abstract class BaseTask extends Task<Void> {
}
public interface Listener {
default void onStart() {
}
default void onRunning() {
}

View File

@ -1,5 +1,6 @@
package cn.octopusyan.alistgui.config;
import atlantafx.base.theme.Theme;
import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.controller.AboutController;
import cn.octopusyan.alistgui.controller.MainController;
@ -36,6 +37,7 @@ public class Context {
private static final Logger log = LoggerFactory.getLogger(Context.class);
private static Scene scene;
private static final IntegerProperty currentViewIndexProperty = new SimpleIntegerProperty(0);
private static final ObjectProperty<Theme> theme = new SimpleObjectProperty<>(ConfigManager.theme());
/**
* 控制器集合
@ -82,6 +84,10 @@ public class Context {
};
}
public static ObjectProperty<Theme> themeProperty() {
return theme;
}
// 获取当前所选时区属性
public static ObjectProperty<Locale> currentLocaleProperty() {
return currentLocale;

View File

@ -1,6 +1,7 @@
package cn.octopusyan.alistgui.controller;
import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.viewModel.AboutViewModule;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
@ -49,6 +50,6 @@ public class AboutController extends BaseController<VBox> {
@FXML
public void checkAListUpdate() {
viewModule.checkAListUpdate();
viewModule.checkUpdate(ConfigManager.aList());
}
}

View File

@ -1,5 +1,6 @@
package cn.octopusyan.alistgui.controller;
import atlantafx.base.theme.Theme;
import cn.octopusyan.alistgui.base.BaseController;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.enums.ProxySetup;
@ -14,6 +15,7 @@ import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,7 +39,7 @@ public class SetupController extends BaseController<VBox> implements Initializab
@FXML
public ComboBox<Locale> languageComboBox;
@FXML
public ComboBox<String> themeComboBox;
public ComboBox<Theme> themeComboBox;
@FXML
public ComboBox<ProxySetup> proxySetupComboBox;
@FXML
@ -59,8 +61,20 @@ public class SetupController extends BaseController<VBox> implements Initializab
@Override
public void initData() {
languageComboBox.setItems(FXCollections.observableList(Context.SUPPORT_LANGUAGE_LIST));
themeComboBox.setItems(FXCollections.observableList(ConfigManager.THEME_NAME_LIST));
themeComboBox.setItems(FXCollections.observableList(ConfigManager.THEME_LIST));
proxySetupComboBox.setItems(FXCollections.observableList(List.of(ProxySetup.values())));
themeComboBox.setConverter(new StringConverter<>() {
@Override
public String toString(Theme object) {
return object.getName();
}
@Override
public Theme fromString(String string) {
return ConfigManager.THEME_MAP.get(string);
}
});
}
@Override
@ -71,7 +85,7 @@ public class SetupController extends BaseController<VBox> implements Initializab
});
languageComboBox.getSelectionModel().select(ConfigManager.language());
themeComboBox.getSelectionModel().select(ConfigManager.themeName());
themeComboBox.getSelectionModel().select(ConfigManager.theme());
proxySetupComboBox.getSelectionModel().select(ConfigManager.proxySetup());
}

View File

@ -3,6 +3,7 @@ package cn.octopusyan.alistgui.manager;
import atlantafx.base.theme.*;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.octopusyan.alistgui.Application;
import cn.octopusyan.alistgui.config.Constants;
@ -12,6 +13,7 @@ import cn.octopusyan.alistgui.model.GuiConfig;
import cn.octopusyan.alistgui.model.ProxyInfo;
import cn.octopusyan.alistgui.model.UpgradeConfig;
import cn.octopusyan.alistgui.model.upgrade.AList;
import cn.octopusyan.alistgui.model.upgrade.Gui;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
@ -22,11 +24,14 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
/**
* 客户端设置
@ -37,22 +42,17 @@ public class ConfigManager {
private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
public static ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
public static final Locale DEFAULT_LANGUAGE = Locale.SIMPLIFIED_CHINESE;
private static GuiConfig guiConfig;
private static UpgradeConfig upgradeConfig;
public static final String DEFAULT_THEME = "Primer Light";
public static List<Theme> THEME_LIST = Arrays.asList(
public static final String DEFAULT_THEME = new PrimerLight().getName();
public static List<Theme> THEME_LIST = List.of(
new PrimerLight(), new PrimerDark(),
new NordLight(), new NordDark(),
new CupertinoLight(), new CupertinoDark(),
new Dracula()
);
public static List<String> THEME_NAME_LIST = Arrays.asList(
"Primer Light", "Primer Dark",
"Nord Light", "Nord Dark",
"Cupertino Light", "Cupertino Dark",
"Dracula"
);
public static Map<String, Theme> THEME_MAP = THEME_LIST.stream()
.collect(Collectors.toMap(Theme::getName, Function.identity()));
private static GuiConfig guiConfig;
static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
@ -61,7 +61,6 @@ public class ConfigManager {
public static void load() {
guiConfig = loadConfig(Constants.GUI_CONFIG_PATH, GuiConfig.class);
upgradeConfig = loadConfig(Constants.UPGRADE_PATH, UpgradeConfig.class);
}
private static <T> T loadConfig(String path, Class<T> clazz) {
@ -77,6 +76,24 @@ public class ConfigManager {
return null;
}
private static <T> void checkFile(File src, Class<T> clazz) throws Exception {
File parent = FileUtil.getParent(src, 1);
if (!parent.exists()) {
boolean wasSuccessful = parent.mkdirs();
objectMapper.writeValue(src, clazz.getDeclaredConstructor().newInstance());
if (!wasSuccessful)
logger.error("{} 创建失败", src.getAbsolutePath());
}
}
public static void save() {
try {
objectMapper.writeValue(new File(Constants.GUI_CONFIG_PATH), guiConfig);
} catch (IOException e) {
logger.error("save config error", e);
}
}
// --------------------------------{ 主题 }------------------------------------------
public static String themeName() {
@ -84,82 +101,15 @@ public class ConfigManager {
}
public static Theme theme() {
return THEME_LIST.get(THEME_NAME_LIST.indexOf(themeName()));
return THEME_MAP.get(themeName());
}
public static void themeName(String themeName) {
int themeIndex = THEME_NAME_LIST.indexOf(themeName);
if (themeIndex < 0) return;
guiConfig.setTheme(themeName);
Application.setUserAgentStylesheet(theme().getUserAgentStylesheet());
public static void theme(Theme theme) {
Application.setUserAgentStylesheet(theme.getUserAgentStylesheet());
guiConfig.setTheme(theme.getName());
}
public static boolean hasProxy() {
if (guiConfig == null)
return false;
ProxyInfo proxyInfo = getProxyInfo();
return proxyInfo != null
&& StringUtils.isNoneEmpty(proxyInfo.getHost())
&& StringUtils.isNoneEmpty(proxyInfo.getPort())
&& Integer.parseInt(proxyInfo.getPort()) > 0;
}
public static ProxyInfo getProxyInfo() {
return guiConfig.getProxyInfo();
}
private static void setProxyInfo(ProxyInfo info) {
guiConfig.setProxyInfo(info);
}
public static String proxyHost() {
if (getProxyInfo() == null) return null;
return getProxyInfo().getHost();
}
public static void proxyHost(String host) {
final Matcher matcher = PatternPool.IPV4.matcher(host);
if (matcher.matches()) {
if (getProxyInfo() == null)
setProxyInfo(new ProxyInfo());
getProxyInfo().setHost(host);
}
}
public static String proxyPort() {
if (getProxyInfo() == null) return null;
return getProxyInfo().getPort();
}
public static int getProxyPort() {
return Integer.parseInt(getProxyInfo().getPort());
}
public static void proxyPort(String port) {
if (NumberUtil.isNumber(port)) {
if (getProxyInfo() == null)
setProxyInfo(new ProxyInfo());
getProxyInfo().setPort(port);
}
}
public static Locale language() {
String language = guiConfig.getLanguage();
return LocaleUtils.toLocale(Optional.ofNullable(language).orElse(DEFAULT_LANGUAGE.toString()));
}
public static void language(Locale locale) {
guiConfig.setLanguage(locale.toString());
}
public static boolean autoStart() {
return guiConfig.getAutoStart();
}
public static void autoStart(Boolean autoStart) {
guiConfig.setAutoStart(autoStart);
}
// --------------------------------{ 网络代理 }------------------------------------------
public static ProxySetup proxySetup() {
return ProxySetup.valueOf(StringUtils.upperCase(guiConfig.getProxySetup()));
@ -186,6 +136,94 @@ public class ConfigManager {
}
}
public static boolean hasProxy() {
if (guiConfig == null)
return false;
ProxyInfo proxyInfo = getProxyInfo();
return proxyInfo != null
&& StringUtils.isNoneEmpty(proxyInfo.getHost())
&& StringUtils.isNoneEmpty(proxyInfo.getPort())
&& Integer.parseInt(proxyInfo.getPort()) > 0;
}
public static ProxyInfo getProxyInfo() {
ProxyInfo proxyInfo = guiConfig.getProxyInfo();
if (proxyInfo == null)
setProxyInfo(new ProxyInfo());
return guiConfig.getProxyInfo();
}
private static void setProxyInfo(ProxyInfo info) {
guiConfig.setProxyInfo(info);
}
public static String proxyHost() {
return getProxyInfo().getHost();
}
public static void proxyHost(String host) {
final Matcher matcher = PatternPool.IPV4.matcher(host);
if (!matcher.matches()) return;
getProxyInfo().setHost(host);
checkProxy();
}
public static String proxyPort() {
return getProxyInfo().getPort();
}
public static int getProxyPort() {
return Integer.parseInt(proxyPort());
}
public static void proxyPort(String port) {
if (!NumberUtil.isNumber(port)) return;
getProxyInfo().setPort(port);
checkProxy();
}
private static void checkProxy() {
if (!hasProxy()) return;
try {
InetSocketAddress address = NetUtil.createAddress(proxyHost(), getProxyPort());
if (NetUtil.isOpen(address, 2000)) {
HttpUtil.getInstance().proxy(proxySetup(), ConfigManager.getProxyInfo());
}
} catch (Exception e) {
logger.error(STR."host=\{proxyHost()},port=\{proxyPort()}", e);
}
}
// --------------------------------{ 语言 }------------------------------------------
public static Locale language() {
String language = guiConfig.getLanguage();
return LocaleUtils.toLocale(Optional.ofNullable(language).orElse(DEFAULT_LANGUAGE.toString()));
}
public static void language(Locale locale) {
guiConfig.setLanguage(locale.toString());
}
// --------------------------------{ 开机自启 }------------------------------------------
public static boolean autoStart() {
return guiConfig.getAutoStart();
}
public static void autoStart(Boolean autoStart) {
guiConfig.setAutoStart(autoStart);
}
// --------------------------------{ 静默启动 }------------------------------------------
public static boolean silentStartup() {
return guiConfig.getSilentStartup();
}
@ -194,35 +232,33 @@ public class ConfigManager {
guiConfig.setSilentStartup(startup);
}
// --------------------------------{ 版本检查 }------------------------------------------
public static UpgradeConfig upgradeConfig() {
return guiConfig.getUpgradeConfig();
}
public static AList aList() {
return upgradeConfig.getAList();
return upgradeConfig().getAList();
}
public static String aListVersion() {
return upgradeConfig.getAList().getVersion();
return aList().getVersion();
}
public static void aListVersion(String version) {
upgradeConfig.getAList().setVersion(version);
aList().setVersion(version);
}
private static <T> void checkFile(File src, Class<T> clazz) throws Exception {
File parent = FileUtil.getParent(src, 1);
if (!parent.exists()) {
boolean wasSuccessful = parent.mkdirs();
objectMapper.writeValue(src, clazz.getDeclaredConstructor().newInstance());
if (!wasSuccessful)
logger.error("{} 创建失败", src.getAbsolutePath());
}
public static Gui gui() {
return upgradeConfig().getGui();
}
public static void save() {
try {
objectMapper.writeValue(new File(Constants.GUI_CONFIG_PATH), guiConfig);
objectMapper.writeValue(new File(Constants.UPGRADE_PATH), upgradeConfig);
} catch (IOException e) {
logger.error("save config error", e);
public static String guiVersion() {
return gui().getVersion();
}
public static void guiVersion(String version) {
gui().setVersion(version);
}
}

View File

@ -1,15 +1,22 @@
package cn.octopusyan.alistgui.manager;
import atlantafx.base.util.BBCodeParser;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 模拟控制台输出
@ -19,18 +26,14 @@ import java.util.Date;
public class ConsoleLog {
public static final String format = "yyyy/MM/dd hh:mm:ss";
private volatile static ConsoleLog log;
private final ScrollPane logAreaSp;
private final VBox textArea;
private final static String CONSOLE_COLOR_PREFIX = "^\033\\[(\\d+)m(.*)\033\\[0m(.*)$";
private final static String CONSOLE_COLOR_SUFFIX = "\033[0m";
private ConsoleLog(ScrollPane logAreaSp, VBox textArea) {
this.textArea = textArea;
this.logAreaSp = logAreaSp;
textArea.maxWidthProperty().bind(logAreaSp.prefWidthProperty());
textArea.heightProperty().subscribe(() -> {
logAreaSp.vvalueProperty().setValue(1);
});
textArea.heightProperty().subscribe(() -> logAreaSp.vvalueProperty().setValue(1));
}
public static ConsoleLog getInstance() {
@ -63,41 +66,120 @@ public class ConsoleLog {
}
public static void info(String tag, String message, Object... param) {
printLog(tag, "INFO", message, param);
printLog(tag, Level.INFO, message, param);
}
public static void warning(String tag, String message, Object... param) {
printLog(tag, "[color=-color-chart-2]WARN[/color]", message, param);
printLog(tag, Level.WARN, message, param);
}
public static void error(String tag, String message, Object... param) {
printLog(tag, "[color=-color-chart-3]ERROR[/color]", message, param);
printLog(tag, Level.ERROR, message, param);
}
public static void msg(String message, Object... param) {
Node text = BBCodeParser.createFormattedText(STR."\{StrUtil.format(message, param)}\n\n");
if (message.contains("\033[")) {
message = resetConsoleColor(message);
}
Node text = BBCodeParser.createFormattedText(STR."\{StrUtil.format(message, param)}");
Platform.runLater(() -> log.textArea.getChildren().add(text));
}
public static void printLog(String tag, String level, String message, Object... param) {
public static void printLog(String tag, Level level, String message, Object... param) {
String time = DateFormatUtils.format(new Date(), format);
time = STR."[color=-color-accent-emphasis]\{time}[/color]";
String levelStr = StringUtils.substringBetween(level, "]", "[");
if (levelStr == null) levelStr = level;
level = StringUtils.replace(level, levelStr, StringUtils.center(levelStr, 5));
String levelStr = resetLevelColor(level);
tag = StringUtils.isEmpty(tag) ? "" : STR."\{tag}: ";
String msg = STR."[color=-color-fg-muted]\{StrUtil.format(message, param)}[/color]";
message = STR."[color=-color-fg-muted]\{StrUtil.format(message, param)}[/color]";
Node text;
if (msg.contains("\n")) {
text = BBCodeParser.createLayout(STR."\{time} \{level} \{tag}\{msg}");
String input = STR."\{time} \{levelStr} \{tag}\{message}";
if (input.contains("\n")) {
text = BBCodeParser.createLayout(input);
} else {
text = BBCodeParser.createFormattedText(STR."\{time} \{level} \{tag}\{msg}");
text = BBCodeParser.createFormattedText(input);
}
Platform.runLater(() -> log.textArea.getChildren().add(text));
}
//============================{ 私有方法 }================================
/**
* 控制台输出颜色
*
* @param msg alist 输出消息
* @return bbcode 颜色文本
*/
private static String resetConsoleColor(String msg) {
try {
String colorCode = ReUtil.get(CONSOLE_COLOR_PREFIX, msg, 1);
String color = StringUtils.lowerCase(Color.valueOf(Integer.parseInt(colorCode)).getColor());
String colorMsg = ReUtil.get(CONSOLE_COLOR_PREFIX, msg, 2);
msg = ReUtil.get(CONSOLE_COLOR_PREFIX, msg, 3);
return color(color, colorMsg) + msg;
} catch (Throwable e) {
e.printStackTrace();
}
return "";
}
/**
* @param level 级别
* @return bbcode 颜色
*/
private static String resetLevelColor(Level level) {
return color(level.getColor(), level.getCode());
}
private static String color(String color, String msg) {
String PREFIX = STR."\{StringUtils.isEmpty(color) ? "" : STR."[color=\{color}]"}";
String SUFFIX = STR."\{StringUtils.isEmpty(color) ? "" : "[/color]"}";
return STR."\{PREFIX}\{msg}\{SUFFIX}";
}
//============================{ 枚举 }================================
@Getter
@RequiredArgsConstructor
public enum Level {
INFO("INFO", null),
WARN("WARN", "color=-color-chart-2"),
ERROR("ERROR", "color=-color-chart-3"),
;
private final String code;
private final String color;
}
@RequiredArgsConstructor
@Getter
enum Color {
BLACK(30, "-color-fg-default"),
RED(31, "-color-danger-fg"),
GREEN(32, "-color-success-fg"),
YELLOW(33, "-color-warning-emphasis"),
BLUE(34, "-color-accent-fg"),
PINKISH_RED(35, "-color-danger-4"),
CYAN(36, "-color-accent-emphasis"),
WHITE(37, "-color-bg-default");
private final int code;
private final String color;
public static final Map<String, Color> NAME_CODE = Arrays.stream(Color.values())
.collect(Collectors.toMap(Color::name, Function.identity()));
public static final Map<Integer, Color> CODE_NAME = Arrays.stream(Color.values())
.collect(Collectors.toMap(Color::getCode, Function.identity()));
public static Color valueOf(int code) {
return CODE_NAME.get(code);
}
}
}

View File

@ -4,6 +4,7 @@ import cn.octopusyan.alistgui.enums.ProxySetup;
import cn.octopusyan.alistgui.model.ProxyInfo;
import cn.octopusyan.alistgui.util.JsonUtil;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import java.io.IOException;
import java.net.InetSocketAddress;
@ -25,6 +26,7 @@ import java.util.Optional;
*/
public class HttpUtil {
private volatile static HttpUtil util;
@Getter
private volatile HttpClient httpClient;
private final HttpConfig httpConfig;

View File

@ -2,6 +2,7 @@ package cn.octopusyan.alistgui.model;
import cn.octopusyan.alistgui.enums.ProxySetup;
import cn.octopusyan.alistgui.manager.ConfigManager;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -17,9 +18,13 @@ public class GuiConfig {
private Boolean autoStart = false;
private Boolean silentStartup = false;
@JsonProperty("proxy")
private ProxyInfo proxyInfo;
@JsonProperty("proxy.testUrl")
private String proxyTestUrl = "http://";
private String proxySetup = ProxySetup.NO_PROXY.getName();
private String language = ConfigManager.DEFAULT_LANGUAGE.toString();
private String theme = ConfigManager.DEFAULT_THEME;
private String proxyTestUrl = "http://";
@JsonProperty("upgrade")
private UpgradeConfig upgradeConfig = new UpgradeConfig();
}

View File

@ -11,8 +11,6 @@ import lombok.Data;
*/
@Data
public class UpgradeConfig {
public static final String RELEASE_API = "https://api.github.com/repos/{}/{}/releases/latest";
public static final String DOWNLOAD_API = "https://github.com/{}/{}/releases/download/{}/{}";
private AList aList = new AList();
private Gui gui = new Gui();
}

View File

@ -1,9 +1,21 @@
package cn.octopusyan.alistgui.model.upgrade;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* @author octopus_yan
*/
public interface UpgradeApp {
@JsonIgnore
default String getReleaseApi() {
return STR."https://api.github.com/repos/\{getOwner()}/\{getRepo()}/releases/latest";
}
@JsonIgnore
default String getDownloadUrl(String version) {
return STR."https://github.com/\{getOwner()}/\{getRepo()}/releases/download/\{version}/\{getReleaseFile()}";
}
String getOwner();
String getRepo();

View File

@ -0,0 +1,158 @@
package cn.octopusyan.alistgui.task;
import cn.octopusyan.alistgui.base.BaseTask;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.manager.http.HttpUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Flow;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* TODO 下载任务
*
* @author octopus_yan
*/
@Slf4j
public class DownloadTask extends BaseTask {
private final String downloadUrl;
public DownloadTask(String downloadUrl) {
this.downloadUrl = downloadUrl;
log.info(STR."downlaod url : \{downloadUrl}");
}
@Override
protected void task() throws Exception {
HttpClient client = HttpUtil.getInstance().getHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(downloadUrl)).build();
// 检查bin目录
File binDir = new File(Constants.BIN_DIR_PATH);
if (!binDir.exists()) {
log.debug(STR."dir [\{Constants.BIN_DIR_PATH}] not exists");
//noinspection ResultOfMethodCallIgnored
binDir.mkdirs();
log.debug(STR."created dir [\{Constants.BIN_DIR_PATH}]");
}
// 下载处理器
var handler = BodyHandler.create(
Path.of(Constants.BIN_DIR_PATH),
StandardOpenOption.CREATE, StandardOpenOption.WRITE
);
// 下载监听
handler.listener((total, progress) -> {
// 输出进度
if (listener != null && listener instanceof Listener lis)
lis.onProgress(total, progress);
});
log.info("download start");
HttpResponse<Path> response = client.send(request, handler);
log.info("download success");
}
public interface Listener extends BaseTask.Listener {
void onProgress(Long total, Long progress);
}
/**
* 自定义下载返回处理
*/
private static class BodyHandler implements HttpResponse.BodyHandler<Path> {
private final HttpResponse.BodyHandler<Path> handler;
private BiConsumer<Long, Long> consumer;
private BodyHandler(HttpResponse.BodyHandler<Path> handler) {
this.handler = handler;
}
public static BodyHandler create(Path directory, OpenOption... openOptions) {
return new BodyHandler(HttpResponse.BodyHandlers.ofFileDownload(directory, openOptions));
}
@Override
public HttpResponse.BodySubscriber<Path> apply(HttpResponse.ResponseInfo responseInfo) {
AtomicLong length = new AtomicLong(-1);
// 获取文件大小
Optional<String> string = responseInfo.headers().firstValue("content-length");
string.ifPresentOrElse(s -> {
length.set(Long.parseLong(s));
log.debug(STR."========={content-length = \{s}}=========");
}, () -> {
String msg = "response not has header [content-length]";
log.error(msg);
});
BodySubscriber subscriber = new BodySubscriber(handler.apply(responseInfo));
subscriber.setConsumer(progress -> consumer.accept(length.get(), progress));
return subscriber;
}
public void listener(BiConsumer<Long, Long> consumer) {
this.consumer = consumer;
}
}
public static class BodySubscriber implements HttpResponse.BodySubscriber<Path> {
private final HttpResponse.BodySubscriber<Path> subscriber;
private final AtomicLong progress = new AtomicLong(0);
@Setter
private Consumer<Long> consumer;
public BodySubscriber(HttpResponse.BodySubscriber<Path> subscriber) {
this.subscriber = subscriber;
}
@Override
public CompletionStage<Path> getBody() {
return subscriber.getBody();
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
subscriber.onSubscribe(subscription);
}
@Override
public void onNext(List<ByteBuffer> item) {
subscriber.onNext(item);
// 记录进度
for (ByteBuffer byteBuffer : item) {
progress.addAndGet(byteBuffer.limit());
}
consumer.accept(progress.get());
}
@Override
public void onError(Throwable throwable) {
subscriber.onError(throwable);
}
@Override
public void onComplete() {
subscriber.onComplete();
consumer.accept(progress.get());
}
}
}

View File

@ -1,9 +1,8 @@
package cn.octopusyan.alistgui.task;
import cn.hutool.core.util.StrUtil;
import cn.octopusyan.alistgui.base.BaseTask;
import cn.octopusyan.alistgui.manager.http.HttpUtil;
import cn.octopusyan.alistgui.model.UpgradeConfig;
import cn.octopusyan.alistgui.model.upgrade.AList;
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
import cn.octopusyan.alistgui.util.JsonUtil;
import cn.octopusyan.alistgui.viewModel.AboutViewModule;
@ -16,31 +15,30 @@ import org.apache.commons.lang3.StringUtils;
* @author octopus_yan
*/
public class UpgradeTask extends BaseTask {
private final AboutViewModule vm;
private final AboutViewModule viewModule;
private final UpgradeApp app;
public UpgradeTask(AboutViewModule viewModel, UpgradeApp app) {
this.vm = viewModel;
this.viewModule = viewModel;
this.app = app;
}
@Override
protected void task() throws Exception {
String releaseApi = StrUtil.format(UpgradeConfig.RELEASE_API, app.getOwner(), app.getRepo());
String responseStr = HttpUtil.getInstance().get(releaseApi, null, null);
String responseStr = HttpUtil.getInstance().get(app.getReleaseApi(), null, null);
JsonNode response = JsonUtil.parseJsonObject(responseStr);
// TODO 校验返回内容
String newVersion = response.get("tag_name").asText();
String downloadUrl = StrUtil.format(UpgradeConfig.DOWNLOAD_API,
app.getOwner(),
app.getRepo(),
newVersion,
app.getReleaseFile()
);
runLater(() -> {
vm.aListUpgradeProperty().setValue(StringUtils.equals(app.getVersion(), newVersion));
vm.aListNewVersionProperty().setValue(newVersion);
if (app instanceof AList) {
viewModule.aListUpgradeProperty().setValue(!StringUtils.equals(app.getVersion(), newVersion));
viewModule.aListNewVersionProperty().setValue(newVersion);
} else {
viewModule.guiUpgradeProperty().setValue(!StringUtils.equals(app.getVersion(), newVersion));
viewModule.guiNewVersionProperty().setValue(newVersion);
}
});
}
}

View File

@ -29,6 +29,11 @@ public class AlertBuilder extends BaseBuilder<AlertBuilder, Alert> {
return this;
}
public AlertBuilder buttons(ButtonType... buttons) {
dialog.getButtonTypes().addAll(buttons);
return this;
}
public AlertBuilder exception(Exception ex) {
dialog.setTitle("Exception Dialog");
dialog.setHeaderText(ex.getClass().getSimpleName());
@ -71,8 +76,9 @@ public class AlertBuilder extends BaseBuilder<AlertBuilder, Alert> {
return Arrays.stream(buttons).map((type) -> {
ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER;
if ("cancel".equals(StringUtils.lowerCase(type)) || "取消".equals(type))
buttonData = ButtonBar.ButtonData.CANCEL_CLOSE;
if ("cancel".equals(StringUtils.lowerCase(type)) || "取消".equals(type)) {
return ButtonType.CANCEL;
}
return new ButtonType(type, buttonData);
}).collect(Collectors.toList());
}

View File

@ -53,6 +53,10 @@ public class AlertUtil {
return confirm().buttons(buttons);
}
public static AlertBuilder confirm(ButtonType... buttons) {
return confirm().buttons(buttons);
}
public static AlertBuilder alert(Alert.AlertType type) {
return new AlertBuilder(mOwner, type);
}
@ -77,7 +81,8 @@ public class AlertUtil {
public interface OnChoseListener {
void confirm();
void cancelOrClose(ButtonType buttonType);
default void cancelOrClose(ButtonType buttonType) {
}
}
public interface OnClickListener {

View File

@ -1,14 +1,8 @@
package cn.octopusyan.alistgui.util.alert;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.scene.control.Dialog;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.util.Objects;
/**
* @author octopus_yan
*/
@ -17,7 +11,6 @@ public abstract class BaseBuilder<T extends BaseBuilder<T, ?>, D extends Dialog<
public BaseBuilder(D dialog, Window mOwner) {
this.dialog = dialog;
icon("/assets/logo.png");
if (mOwner != null)
this.dialog.initOwner(mOwner);
}
@ -37,18 +30,6 @@ public abstract class BaseBuilder<T extends BaseBuilder<T, ?>, D extends Dialog<
return (T) this;
}
public T icon(String path) {
return icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString()));
}
public T icon(Image image) {
ObservableList<Image> icons = getStage().getIcons();
if (icons.isEmpty()) {
Platform.runLater(() -> icons.add(image));
}
return (T) this;
}
public D getDialog() {
return dialog;
}
@ -58,11 +39,7 @@ public abstract class BaseBuilder<T extends BaseBuilder<T, ?>, D extends Dialog<
}
public void close() {
if(dialog.isShowing())
if (dialog.isShowing())
dialog.close();
}
private Stage getStage() {
return (Stage) dialog.getDialogPane().getScene().getWindow();
}
}

View File

@ -52,7 +52,7 @@ public class ProgressBuilder extends BaseBuilder<ProgressBuilder, Dialog<Void>>
HBox hBox = new HBox();
hBox.setPrefWidth(350);
hBox.setAlignment(Pos.CENTER);
hBox.setPadding(new Insets(0, 0, 0, 0));
hBox.setPadding(new Insets(10, 0, 10, 0));
// 取消按钮
Button cancel = new Button(Context.getLanguageBinding("label.cancel").get());

View File

@ -1,14 +1,28 @@
package cn.octopusyan.alistgui.viewModel;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.octopusyan.alistgui.base.BaseTask;
import cn.octopusyan.alistgui.config.Constants;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.manager.ConfigManager;
import cn.octopusyan.alistgui.manager.ConsoleLog;
import cn.octopusyan.alistgui.model.upgrade.AList;
import cn.octopusyan.alistgui.model.upgrade.UpgradeApp;
import cn.octopusyan.alistgui.task.DownloadTask;
import cn.octopusyan.alistgui.task.UpgradeTask;
import cn.octopusyan.alistgui.util.alert.AlertBuilder;
import cn.octopusyan.alistgui.util.alert.AlertUtil;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.scene.control.ButtonType;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.InputStream;
import java.util.zip.ZipFile;
/**
* 关于
*
@ -19,6 +33,9 @@ public class AboutViewModule {
private final StringProperty aListVersion = new SimpleStringProperty(ConfigManager.aListVersion());
private final StringProperty aListNewVersion = new SimpleStringProperty("");
private final BooleanProperty aListUpgrade = new SimpleBooleanProperty(false);
private final StringProperty guiVersion = new SimpleStringProperty(ConfigManager.guiVersion());
private final StringProperty guiNewVersion = new SimpleStringProperty("");
private final BooleanProperty guiUpgrade = new SimpleBooleanProperty(false);
public AboutViewModule() {
aListVersion.addListener((_, _, newValue) -> ConfigManager.aListVersion(newValue));
@ -36,38 +53,173 @@ public class AboutViewModule {
return aListNewVersion;
}
public StringProperty guiVersionProperty() {
return guiVersion;
}
public StringProperty guiNewVersionProperty() {
return guiNewVersion;
}
public BooleanProperty guiUpgradeProperty() {
return guiUpgrade;
}
/**
* 检查alist更新
* 检查更新
*/
public void checkAListUpdate() {
var task = new UpgradeTask(this, ConfigManager.aList());
task.onListen(new BaseTask.Listener() {
@Override
public void onSucceeded() {
AlertUtil.confirm()
public void checkUpdate(UpgradeApp app) {
// 检查任务
startUpgrade(app, () -> {
// 判断 检查的应用
boolean tag = app instanceof AList;
boolean upgrade = tag ? aListUpgrade.get() : guiUpgrade.get();
String version = tag ? aListVersion.get() : guiVersion.get();
String newVersion = tag ? aListNewVersion.get() : guiNewVersion.get();
String title = Context.getLanguageBinding(STR."about.\{tag ? "alist" : "app"}.update").getValue();
String currentLabel = Context.getLanguageBinding("update.current").get();
String newLabel = Context.getLanguageBinding("update.remote").get();
String header = Context.getLanguageBinding(STR."update.upgrade.\{upgrade ? "new" : "not"}").get();
String msg = STR."\{app.getRepo()}\{upgrade ? "" : STR." \{version}"} \{header} \{upgrade ? newVersion : ""}";
log.info(msg);
ConsoleLog.info(msg);
AlertBuilder builder = upgrade ? AlertUtil.confirm() : AlertUtil.info();
builder.title(title)
.header(header)
.content(STR."""
当前版本 : \{aListVersion.get()}
最新版本 : \{aListNewVersion.get()}
\{currentLabel} : \{version}
\{newLabel} : \{newVersion}
""")
.title(Context.getLanguageBinding("about.alist.update").getValue())
.show(new AlertUtil.OnChoseListener() {
.show(() -> {
if (upgrade) startDownload(app, newVersion);
});
});
}
private void startUpgrade(UpgradeApp app, Runnable runnable) {
// 检查更新的任务
var task = new UpgradeTask(this, app);
// 加载弹窗
final var progress = AlertUtil.progress();
progress.title(Context.getLanguageBinding("proxy.test.title").get());
progress.onCancel(task::cancel);
// 任务监听
task.onListen(new BaseTask.Listener() {
@Override
public void confirm() {
log.info("========confirm==========");
public void onStart() {
String msg = STR."start update \{app.getRepo()}...";
log.info(msg);
ConsoleLog.info(msg);
}
@Override
public void cancelOrClose(ButtonType buttonType) {
log.info("========cancelOrClose==========");
public void onRunning() {
progress.show();
}
});
@Override
public void onSucceeded() {
progress.close();
if (runnable != null) runnable.run();
}
@Override
public void onFailed(Throwable throwable) {
String msg = STR."\{app.getRepo()} check version error";
log.error(msg, throwable);
ConsoleLog.error(STR."\{msg} : \{throwable.getMessage()}");
AlertUtil.exception(new Exception(throwable)).show();
}
});
// 执行任务
task.execute();
}
/**
* 下载文件
*
* @param app 应用
* @param version 下载版本
*/
private void startDownload(UpgradeApp app, String version) {
DownloadTask downloadTask = new DownloadTask(app.getDownloadUrl(version));
downloadTask.onListen(new DownloadTask.Listener() {
private volatile int lastProgress = 0;
@Override
public void onStart() {
String msg = STR."download \{app.getRepo()} start";
log.info(msg);
ConsoleLog.info(msg);
}
@Override
public void onProgress(Long total, Long progress) {
int a = (int) (((double) progress / total) * 100);
if (a % 10 == 0) {
if (a != lastProgress) {
lastProgress = a;
String msg = STR."\{app.getRepo()} \{a} %";
log.info(STR."download \{msg}");
ConsoleLog.info(msg);
}
}
}
@Override
public void onSucceeded() {
String msg = STR."download \{app.getRepo()} success";
log.info(msg);
ConsoleLog.info(msg);
// 解压并删除文件
unzip(app);
Platform.runLater(() -> aListVersion.setValue(aListNewVersion.getValue()));
}
@Override
public void onFailed(Throwable throwable) {
log.error("下载失败", throwable);
ConsoleLog.error(STR."下载失败 => \{throwable.getMessage()}");
}
});
downloadTask.execute();
}
private void unzip(UpgradeApp app) {
File file = new File(Constants.BIN_DIR_PATH + File.separator + app.getReleaseFile());
ZipFile zipFile = ZipUtil.toZipFile(file, CharsetUtil.defaultCharset());
ZipUtil.read(zipFile, zipEntry -> {
String path = zipEntry.getName();
if (FileUtil.isWindows()) {
// Win系统下
path = StrUtil.replace(path, "*", "_");
}
final File outItemFile = FileUtil.file(Constants.BIN_DIR_PATH, path);
if (zipEntry.isDirectory()) {
// 目录
//noinspection ResultOfMethodCallIgnored
outItemFile.mkdirs();
} else {
InputStream in = ZipUtil.getStream(zipFile, zipEntry);
// 文件
FileUtil.writeFromStream(in, outItemFile, false);
log.info(STR."unzip ==> \{outItemFile.getAbsoluteFile()}");
}
});
// 解压完成后删除
FileUtil.del(file);
}
}

View File

@ -1,5 +1,6 @@
package cn.octopusyan.alistgui.viewModel;
import atlantafx.base.theme.Theme;
import cn.octopusyan.alistgui.base.BaseTask;
import cn.octopusyan.alistgui.config.Context;
import cn.octopusyan.alistgui.enums.ProxySetup;
@ -22,7 +23,7 @@ import java.util.Locale;
public class SetupViewModel {
private final BooleanProperty autoStart = new SimpleBooleanProperty(ConfigManager.autoStart());
private final BooleanProperty silentStartup = new SimpleBooleanProperty(ConfigManager.silentStartup());
private final StringProperty theme = new SimpleStringProperty(ConfigManager.themeName());
private final ObjectProperty<Theme> theme = new SimpleObjectProperty<>(ConfigManager.theme());
private final StringProperty proxyHost = new SimpleStringProperty(ConfigManager.proxyHost());
private final StringProperty proxyPort = new SimpleStringProperty(ConfigManager.proxyPort());
private final ObjectProperty<Locale> language = new SimpleObjectProperty<>(ConfigManager.language());
@ -31,7 +32,8 @@ public class SetupViewModel {
public SetupViewModel() {
theme.addListener((_, _, newValue) -> ConfigManager.themeName(newValue));
theme.bindBidirectional(Context.themeProperty());
theme.addListener((_, _, newValue) -> ConfigManager.theme(newValue));
autoStart.addListener((_, _, newValue) -> ConfigManager.autoStart(newValue));
silentStartup.addListener((_, _, newValue) -> ConfigManager.silentStartup(newValue));
proxySetup.addListener((_, _, newValue) -> ConfigManager.proxySetup(newValue));
@ -41,7 +43,7 @@ public class SetupViewModel {
language.addListener((_, _, newValue) -> Context.setLanguage(newValue));
}
public StringProperty themeProperty() {
public ObjectProperty<Theme> themeProperty() {
return theme;
}

View File

@ -29,6 +29,6 @@
<Label styleClass="shield-name" text="%about.app.version"/>
<Label styleClass="shield-version" text="v${project.version}"/>
</HBox>
<Button fx:id="checkAppVersion" text="%about.app.update"/>
<Button fx:id="checkAListVersion" onAction="#checkAListUpdate" text="%about.alist.update"/>
<Button fx:id="checkAppVersion" styleClass="flat" text="%about.app.update"/>
<Button fx:id="checkAListVersion" styleClass="flat" onAction="#checkAListUpdate" text="%about.alist.update"/>
</VBox>

View File

@ -32,5 +32,10 @@ about.app.version=GUI \u7248\u672C
about.alist.update=\u68C0\u67E5 AList \u7248\u672C
about.app.update=\u68C0\u67E5 GUI \u7248\u672C
setup.theme=\u4E3B\u9898
update.current=\u5F53\u524D
update.remote=\u6700\u65B0
update.upgrade.not=\u5DF2\u662F\u6700\u65B0\u7248\u672C
update.upgrade.new=\u68C0\u67E5\u5230\u65B0\u7248\u672C

View File

@ -32,5 +32,9 @@ about.app.version=GUI Version
about.alist.update=Check AList Version
about.app.update=Check GUI Version
setup.theme=Theme
update.current=Current
update.remote=Latest
update.upgrade.not=It is already the latest version
update.upgrade.new=Detected a new version

View File

@ -32,5 +32,9 @@ about.app.version=GUI \u7248\u672C
about.alist.update=\u68C0\u67E5 AList \u7248\u672C
about.app.update=\u68C0\u67E5 GUI \u7248\u672C
setup.theme=\u4E3B\u9898
update.current=\u5F53\u524D
update.remote=\u6700\u65B0
update.upgrade.not=\u5DF2\u662F\u6700\u65B0\u7248\u672C
update.upgrade.new=\u68C0\u67E5\u5230\u65B0\u7248\u672C