-
Notifications
You must be signed in to change notification settings - Fork 896
[Enhancement] 后台自动下载更新 #6092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Enhancement] 后台自动下载更新 #6092
Changes from all commits
93d4917
e8f258c
1dc80c2
38c1abe
d7e2b63
43a7ee1
3a97ce2
3436034
dd52307
630acc0
f29058e
2d2d789
68376b3
b3e78c2
5204738
302d4d8
2511138
6e732bf
bcf7608
636cca3
aeeabb4
d5e1508
d098e17
1fbab4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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.Map; | ||
| import java.util.Optional; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| 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 ConcurrentHashMap<>(); | ||
|
|
||
| public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException { | ||
| try { | ||
| JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(url), JsonObject.class); | ||
|
|
@@ -53,6 +62,29 @@ 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()); | ||
| try { | ||
| Files.deleteIfExists(downloaded); | ||
| } catch (IOException ignored) { | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+65
to
+86
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 在后台自动下载更新时, 由于 建议引入一个并发安全的集合(例如 private static final java.util.Set<RemoteVersion> downloadingVersions = ConcurrentHashMap.newKeySet();
public void tryDownload() {
Path downloaded = downloadCache.get(this);
if (downloaded != null && FileUtils.verifyHash(downloaded, integrityCheck().algorithm(), integrityCheck().checksum())) return;
if (!downloadingVersions.add(this)) return;
try {
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());
try {
Files.deleteIfExists(downloaded);
} catch (IOException ignored) {
}
}
} finally {
downloadingVersions.remove(this);
}
} |
||
|
|
||
| public enum Type { | ||
| JAR | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -21,7 +21,6 @@ | |||||
| import javafx.beans.binding.Bindings; | ||||||
| import javafx.beans.binding.BooleanBinding; | ||||||
| import javafx.beans.property.*; | ||||||
| import javafx.beans.value.ObservableBooleanValue; | ||||||
| import org.jackhuang.hmcl.Metadata; | ||||||
| import org.jackhuang.hmcl.util.io.NetworkUtils; | ||||||
| import org.jackhuang.hmcl.util.versioning.VersionNumber; | ||||||
|
|
@@ -38,25 +37,14 @@ private UpdateChecker() { | |||||
| } | ||||||
|
|
||||||
| private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>(); | ||||||
| private static final BooleanBinding outdated = Bindings.createBooleanBinding( | ||||||
| () -> { | ||||||
| RemoteVersion latest = latestVersion.get(); | ||||||
| if (latest == null || isDevelopmentVersion(Metadata.VERSION)) { | ||||||
| return false; | ||||||
| } else if (latest.force() | ||||||
| || Metadata.isNightly() | ||||||
| || latest.channel() == UpdateChannel.NIGHTLY | ||||||
| || latest.channel() != UpdateChannel.getChannel()) { | ||||||
| return !latest.version().equals(Metadata.VERSION); | ||||||
| } else { | ||||||
| return VersionNumber.compare(Metadata.VERSION, latest.version()) < 0; | ||||||
| } | ||||||
| }, | ||||||
| latestVersion); | ||||||
| private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false); | ||||||
| private static final ReadOnlyBooleanWrapper outdated = new ReadOnlyBooleanWrapper(false); | ||||||
| private static final IntegerProperty runningThreads = new SimpleIntegerProperty(0); | ||||||
| private static final BooleanBinding checkingUpdate = Bindings.createBooleanBinding(() -> runningThreads.get() > 0, runningThreads); | ||||||
|
|
||||||
| private static UpdateTarget desiredTarget = null; | ||||||
|
|
||||||
| public static void init() { | ||||||
| requestCheckUpdate(UpdateChannel.getChannel(), settings().acceptPreviewUpdateProperty().get()); | ||||||
| requestCheckUpdate(UpdateChannel.getChannel(), settings().acceptPreviewUpdateProperty().get(), settings().autoDownloadUpdateProperty().get()); | ||||||
| } | ||||||
|
|
||||||
| public static RemoteVersion getLatestVersion() { | ||||||
|
|
@@ -71,16 +59,16 @@ public static boolean isOutdated() { | |||||
| return outdated.get(); | ||||||
| } | ||||||
|
|
||||||
| public static ObservableBooleanValue outdatedProperty() { | ||||||
| public static ReadOnlyBooleanProperty outdatedProperty() { | ||||||
| return outdated; | ||||||
| } | ||||||
|
|
||||||
| public static boolean isCheckingUpdate() { | ||||||
| return checkingUpdate.get(); | ||||||
| } | ||||||
|
|
||||||
| public static ReadOnlyBooleanProperty checkingUpdateProperty() { | ||||||
| return checkingUpdate.getReadOnlyProperty(); | ||||||
| public static BooleanBinding checkingUpdateProperty() { | ||||||
| return checkingUpdate; | ||||||
| } | ||||||
|
|
||||||
| private static RemoteVersion checkUpdate(UpdateChannel channel, boolean preview) throws IOException { | ||||||
|
|
@@ -101,29 +89,59 @@ private static boolean isDevelopmentVersion(String version) { | |||||
| version.contains("SNAPSHOT"); // eg. 3.5.SNAPSHOT | ||||||
| } | ||||||
|
|
||||||
| public static void requestCheckUpdate(UpdateChannel channel, boolean preview) { | ||||||
| private static boolean checkOutdated(RemoteVersion latest) { | ||||||
| if (latest == null || isDevelopmentVersion(Metadata.VERSION)) { | ||||||
| return false; | ||||||
| } else if (latest.force() | ||||||
| || Metadata.isNightly() | ||||||
| || latest.channel() == UpdateChannel.NIGHTLY | ||||||
| || latest.channel() != UpdateChannel.getChannel()) { | ||||||
| return !latest.version().equals(Metadata.VERSION); | ||||||
| } else { | ||||||
| return VersionNumber.compare(Metadata.VERSION, latest.version()) < 0; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| public static void requestCheckUpdate(UpdateChannel channel, boolean preview, boolean download) { | ||||||
|
ToobLac marked this conversation as resolved.
|
||||||
| requestCheckUpdate(new UpdateTarget(channel, preview, download)); | ||||||
| } | ||||||
|
|
||||||
| private static void requestCheckUpdate(UpdateTarget target) { | ||||||
| Platform.runLater(() -> { | ||||||
| if (isCheckingUpdate()) | ||||||
| if (isCheckingUpdate() && target.concealedBy(desiredTarget)) | ||||||
| return; | ||||||
| checkingUpdate.set(true); | ||||||
| runningThreads.set(runningThreads.get() + 1); | ||||||
| desiredTarget = target; | ||||||
|
|
||||||
| thread(() -> { | ||||||
| RemoteVersion result = null; | ||||||
| boolean b = false; | ||||||
| try { | ||||||
| result = checkUpdate(channel, preview); | ||||||
| LOG.info("Latest version (" + channel + ", preview=" + preview + ") is " + result); | ||||||
| result = checkUpdate(target.channel(), target.preview()); | ||||||
| b = checkOutdated(result); | ||||||
| LOG.info("Latest version (" + target.channel() + ", preview=" + target.preview() + ") is " + result); | ||||||
| if (target.download() && b) result.tryDownload(); | ||||||
| } catch (Throwable e) { | ||||||
| LOG.warning("Failed to check for update", e); | ||||||
| } | ||||||
| boolean isOutdated = b; | ||||||
|
|
||||||
| RemoteVersion finalResult = result; | ||||||
| Platform.runLater(() -> { | ||||||
| if (finalResult != null) { | ||||||
| if (finalResult != null && target.concealedBy(desiredTarget)) { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 此处使用 如果前一个请求 实际上,只要
Suggested change
|
||||||
| latestVersion.set(finalResult); | ||||||
| outdated.set(isOutdated); | ||||||
| } | ||||||
| checkingUpdate.set(false); | ||||||
| runningThreads.set(runningThreads.get() - 1); | ||||||
| }); | ||||||
| }, "Update Checker", true); | ||||||
| }); | ||||||
| } | ||||||
|
|
||||||
| private record UpdateTarget(UpdateChannel channel, boolean preview, boolean download) { | ||||||
| public boolean concealedBy(UpdateTarget other) { | ||||||
| if (other == null) return false; | ||||||
| return other.channel == channel && other.preview == preview && (!download || other.download); | ||||||
| } | ||||||
| } | ||||||
|
Comment on lines
+141
to
+146
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 建议在 private record UpdateTarget(UpdateChannel channel, boolean preview, boolean download) {
public boolean concealedBy(UpdateTarget other) {
if (other == null) return false;
return other.channel == channel && other.preview == preview && (!download || other.download);
}
public boolean isSameSource(UpdateTarget other) {
if (other == null) return false;
return other.channel == channel && other.preview == preview;
}
} |
||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -573,4 +574,17 @@ public static EnumSet<PosixFilePermission> parsePosixFilePermission(int unixMode | |
|
|
||
| return permissions; | ||
| } | ||
|
|
||
| public static boolean verifyHash(Path file, String algorithm, String hash) { | ||
| if (Files.isRegularFile(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; | ||
| } | ||
| } | ||
|
Comment on lines
+578
to
+589
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 为了提高工具类方法的健壮性,建议在 public static boolean verifyHash(Path file, String algorithm, String hash) {
if (file == null || algorithm == null || hash == null) {
return false;
}
if (Files.isRegularFile(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;
}
} |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.