Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,21 @@ public void setDisableAutoShowUpdateDialog(boolean disableAutoShowUpdateDialog)
this.disableAutoShowUpdateDialog.set(disableAutoShowUpdateDialog);
}

@SerializedName("autoDownloadUpdate")
private final BooleanProperty autoDownloadUpdate = new SimpleBooleanProperty(false);

public BooleanProperty autoDownloadUpdateProperty() {
return autoDownloadUpdate;
}

public boolean isAutoDownloadUpdate() {
return autoDownloadUpdate.get();
}

public void setAutoDownloadUpdate(boolean autoDownloadUpdate) {
this.autoDownloadUpdate.set(autoDownloadUpdate);
}

@SerializedName("disableAprilFools")
private final BooleanProperty disableAprilFools = new SimpleBooleanProperty(false);

Expand Down
30 changes: 23 additions & 7 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.jfoenix.controls.JFXButton;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.css.PseudoClass;
Expand Down Expand Up @@ -86,7 +87,6 @@ public SettingsPage() {
{
ObjectProperty<UpdateChannel> updateChannel;
{

JFXButton updateButton = FXUtils.newToggleButton4(SVG.UPDATE, 20);
updateButton.setOnAction(e -> onUpdate());
updateButton.setPadding(Insets.EMPTY);
Expand Down Expand Up @@ -139,17 +139,13 @@ protected int getTrailingTextIndex() {
updatePaneList.getContent().add(updatePane);
}

BooleanProperty preview;
{
LineToggleButton previewPane = new LineToggleButton();
previewPane.setTitle(i18n("update.preview"));
previewPane.setSubtitle(i18n("update.preview.subtitle"));
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());

InvalidationListener checkUpdateListener = e -> {
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
};
updateChannel.addListener(checkUpdateListener);
previewPane.selectedProperty().addListener(checkUpdateListener);
preview = previewPane.selectedProperty();

updatePaneList.getContent().add(previewPane);
}
Expand All @@ -162,6 +158,26 @@ protected int getTrailingTextIndex() {
updatePaneList.getContent().add(disableAutoShowUpdateDialogPane);
}

BooleanProperty autoDownloadUpdate;
{
LineToggleButton autoDownloadUpdatePane = new LineToggleButton();
autoDownloadUpdatePane.setTitle(i18n("update.auto_download"));
autoDownloadUpdatePane.setSubtitle(i18n("update.auto_download.subtitle"));
autoDownloadUpdatePane.selectedProperty().bindBidirectional(config().autoDownloadUpdateProperty());
autoDownloadUpdate = autoDownloadUpdatePane.selectedProperty();

updatePaneList.getContent().add(autoDownloadUpdatePane);
}

{
InvalidationListener checkUpdateListener = e -> {
UpdateChecker.requestCheckUpdate(updateChannel.get(), preview.get(), autoDownloadUpdate.get());
};
updateChannel.addListener(checkUpdateListener);
preview.addListener(checkUpdateListener);
autoDownloadUpdate.addListener(checkUpdateListener);
}

rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("update")), updatePaneList);
}

Expand Down
28 changes: 28 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,24 @@
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static org.jackhuang.hmcl.util.logging.Logger.LOG;

public record RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck,
boolean preview, boolean force) {

public static final Map<RemoteVersion, Path> downloadCache = new HashMap<>();

public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException {
try {
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(url), JsonObject.class);
Expand All @@ -53,6 +62,25 @@ public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String
return "[" + version + " from " + url + "]";
}

public void tryDownload() {
Path downloaded = downloadCache.get(this);
if (downloaded != null && FileUtils.verifyHash(downloaded, integrityCheck().algorithm(), integrityCheck().checksum())) return;

try {
downloaded = Files.createTempFile("hmcl-update-", ".jar");
} catch (IOException e) {
LOG.warning("Failed to create temp file", e);
return;
}

var executor = new HMCLDownloadTask(this, downloaded).executor();
if (executor.test()) {
downloadCache.put(this, downloaded);
} else {
LOG.warning("Failed to download update for " + this, executor.getException());
}
}

public enum Type {
JAR
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
public enum UpdateChannel {
STABLE("stable"),
DEVELOPMENT("dev"),
NIGHTLY("nightly");
NIGHTLY("dev");

public final String channelName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private UpdateChecker() {
private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);

public static void init() {
requestCheckUpdate(UpdateChannel.getChannel(), config().isAcceptPreviewUpdate());
requestCheckUpdate(UpdateChannel.getChannel(), config().isAcceptPreviewUpdate(), config().isAutoDownloadUpdate());
}

public static RemoteVersion getLatestVersion() {
Expand Down Expand Up @@ -101,7 +101,7 @@ private static boolean isDevelopmentVersion(String version) {
version.contains("SNAPSHOT"); // eg. 3.5.SNAPSHOT
}

public static void requestCheckUpdate(UpdateChannel channel, boolean preview) {
public static void requestCheckUpdate(UpdateChannel channel, boolean preview, boolean download) {
Platform.runLater(() -> {
if (isCheckingUpdate())
return;
Expand All @@ -112,6 +112,7 @@ public static void requestCheckUpdate(UpdateChannel channel, boolean preview) {
try {
result = checkUpdate(channel, preview);
LOG.info("Latest version (" + channel + ", preview=" + preview + ") is " + result);
if (download) result.tryDownload();
} catch (Throwable e) {
LOG.warning("Failed to check for update", e);
}
Expand Down
35 changes: 20 additions & 15 deletions HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.SwingUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;

Expand Down Expand Up @@ -106,22 +107,26 @@ public static void updateFrom(RemoteVersion version) {
}

Controllers.dialog(new UpgradeDialog(version, () -> {
Path downloaded;
try {
downloaded = Files.createTempFile("hmcl-update-", ".jar");
} catch (IOException e) {
LOG.warning("Failed to create temp file", e);
return;
}

Task<?> task = new HMCLDownloadTask(version, downloaded);
Path downloaded = RemoteVersion.downloadCache.get(version);
TaskExecutor executor;
if (downloaded != null && FileUtils.verifyHash(downloaded, version.integrityCheck().algorithm(), version.integrityCheck().checksum())) {
executor = Task.completed(null).executor();
} else {
try {
downloaded = Files.createTempFile("hmcl-update-", ".jar");
} catch (IOException e) {
LOG.warning("Failed to create temp file", e);
return;
}

TaskExecutor executor = task.executor();
Controllers.taskDialog(executor, i18n("message.downloading"), TaskCancellationAction.NORMAL);
Task<?> task = new HMCLDownloadTask(version, downloaded);
executor = task.executor();
Controllers.taskDialog(executor, i18n("message.downloading"), TaskCancellationAction.NORMAL);
}
final Path finalDownloaded = downloaded;
thread(() -> {
boolean success = executor.test();

if (success) {
if (executor.test()) {
RemoteVersion.downloadCache.put(version, finalDownloaded);
try {
if (!IntegrityChecker.isSelfVerified() && !IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK) {
throw new IOException("Current JAR is not verified");
Expand Down Expand Up @@ -150,7 +155,7 @@ public static void updateFrom(RemoteVersion version) {
// Ignore
}

requestUpdate(downloaded, getCurrentLocation());
requestUpdate(finalDownloaded, getCurrentLocation());
EntryPoint.exit(0);
} catch (IOException e) {
LOG.warning("Failed to update to " + version, e);
Expand Down
2 changes: 2 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1630,6 +1630,8 @@ unofficial.hint=You are using an unofficial build of HMCL. We cannot guarantee i

update=Update
update.accept=Update
update.auto_download=Auto download update
update.auto_download.subtitle=Automatically download launcher updates and notify when ready to install.
update.changelog=Changelog
update.channel.dev=Beta
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Stable channel builds.\n\
Expand Down
2 changes: 2 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,8 @@ unofficial.hint=你正在使用第三方提供的 HMCL。我們無法保證其

update=啟動器更新
update.accept=更新
update.auto_download=自動下載更新
update.auto_download.subtitle=自動在背景下載更新,下載完成後顯示通知
update.changelog=更新日誌
update.channel.dev=開發版
update.channel.dev.hint=你正在使用 HMCL 開發版。開發版包含一些未在穩定版中包含的測試性功能,僅用於體驗新功能。開發版功能未受充分驗證,使用起來可能不穩定!\n\
Expand Down
2 changes: 2 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1425,6 +1425,8 @@ unofficial.hint=你正在使用非官方构建的 HMCL。我们无法保证其

update=启动器更新
update.accept=更新
update.auto_download=自动下载更新
update.auto_download.subtitle=自动在后台下载更新,下载完成后显示通知
update.changelog=更新日志
update.channel.dev=开发版
update.channel.dev.hint=你正在使用 HMCL 开发版。开发版包含一些未在稳定版中包含的测试性功能,仅用于体验新功能。开发版功能未受充分验证,使用起来可能不稳定!<a href="https://hmcl.huangyuhui.net/download">下载稳定版</a>\n\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,7 @@ protected Path getFile(String algorithm, String hash) {
protected boolean fileExists(String algorithm, String hash) {
if (hash == null) return false;
Path file = getFile(algorithm, hash);
if (Files.exists(file)) {
try {
return DigestUtils.digestToString(algorithm, file).equalsIgnoreCase(hash);
} catch (IOException e) {
return false;
}
} else {
return false;
}
return FileUtils.verifyHash(file, algorithm, hash);
}

public void tryCacheFile(Path path, String algorithm, String hash) throws IOException {
Expand Down
14 changes: 14 additions & 0 deletions HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.glavo.chardet.DetectedCharset;
import org.glavo.chardet.UniversalDetector;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
Expand Down Expand Up @@ -559,4 +560,17 @@ public static EnumSet<PosixFilePermission> parsePosixFilePermission(int unixMode

return permissions;
}

public static boolean verifyHash(Path file, String algorithm, String hash) {
if (Files.exists(file)) {
try {
return DigestUtils.digestToString(algorithm, file).equalsIgnoreCase(hash);
} catch (IOException e) {
LOG.warning("Failed to verify hash for file " + file, e);
return false;
}
} else {
return false;
}
}
}