[Enhancement] 后台自动下载更新#6092
Conversation
Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
…More/HMCL into auto-update-download
# Conflicts: # HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces an automatic update download feature, allowing the launcher to download updates in the background and cache them for installation. It adds the corresponding UI settings, localization strings, and a reusable hash verification utility. The review feedback highlights several critical improvements: implementing a version check to prevent downloading updates when the launcher is already up-to-date, using a thread-safe ConcurrentHashMap for the download cache to avoid concurrency issues, reverting an apparently erroneous change to the NIGHTLY channel name, and using Files.isRegularFile instead of Files.exists to prevent attempting to hash directories.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces an automatic update download feature, allowing launcher updates to be downloaded in the background once detected. It adds a new setting toggle in the UI, caches downloaded updates, and refactors hash verification into a reusable utility. Feedback highlights two key issues: first, running the download synchronously within the update checker thread blocks the update-checking UI state, which should be resolved by offloading the download to a separate thread; second, failed downloads leave orphaned temporary files, which should be cleaned up upon failure.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
22201ad to
aeeabb4
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces an "Auto download update" feature to automatically download launcher updates in the background. It adds a configuration property, a UI toggle in the settings page, caching of downloaded updates, and refactors hash verification into a reusable utility method. The review feedback points out that the auto-download setting is ignored (hardcoded to false) when the update channel or preview settings are changed, and suggests respecting the user's preference.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces an automatic background download feature for launcher updates, adding a new setting toggle in the UI, caching downloaded updates, and refactoring hash verification into a reusable utility method. The review feedback highlights a potential issue in UpdateChecker where valid update information might be discarded due to overly strict target matching, a concurrency issue in RemoteVersion.tryDownload() that could lead to duplicate downloads under race conditions, and a recommendation to add defensive null checks to the new FileUtils.verifyHash method.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| RemoteVersion finalResult = result; | ||
| Platform.runLater(() -> { | ||
| if (finalResult != null) { | ||
| if (finalResult != null && target.concealedBy(desiredTarget)) { |
There was a problem hiding this comment.
此处使用 target.concealedBy(desiredTarget) 来判断是否更新 UI 上的版本信息过于严格。
如果前一个请求 target1 启用了下载(download=true),而最新的请求 target2 未启用下载(download=false),那么当 target1 的线程执行完毕时,target1.concealedBy(target2) 将返回 false。这会导致 target1 成功获取到的最新版本信息被丢弃,无法更新到 UI 上。
实际上,只要 channel 和 preview 相同,获取到的最新版本信息就是完全一致且有效的。建议在 UpdateTarget 中增加一个 isSameSource 方法,仅比较 channel 和 preview,并在此处使用它。
| if (finalResult != null && target.concealedBy(desiredTarget)) { | |
| if (finalResult != null && target.isSameSource(desiredTarget)) { |
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
建议在 UpdateTarget 中增加 isSameSource 方法,用于在不考虑 download 属性的情况下,判断两个更新目标是否指向相同的更新通道和预览设置。这可以避免在更新 UI 版本信息时,因 download 属性不同而错误地丢弃有效的更新结果。
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;
}
}| 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) { | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
在后台自动下载更新时,tryDownload() 可能会被并发调用(例如,用户在短时间内多次手动触发更新检查,或者多个模块同时请求检查)。
由于 tryDownload() 中的“检查缓存 -> 创建临时文件 -> 开始下载”操作不是原子的,可能会导致多个线程同时为同一个 RemoteVersion 创建临时文件并重复下载,造成网络资源和磁盘空间的浪费。
建议引入一个并发安全的集合(例如 ConcurrentHashMap.newKeySet())来记录当前正在下载的 RemoteVersion,从而避免重复的并发下载。
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 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
为了提高工具类方法的健壮性,建议在 verifyHash 方法中添加对 file、algorithm 和 hash 的防御性空检查(Null Checks),避免在传入 null 时抛出 NullPointerException。
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;
}
}|
Replaced by #6255 |
|
Replaced by #6255 |
Closes #4352
目前默认关闭,开启后在检查更新成功后自动下载更新然后再弹窗