From c365c9e8a4db0b6da45bcf9cc690af5a032c67bc Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 15:09:47 +0800 Subject: [PATCH 01/17] refactor: streamline addon update checks and improve type handling --- .../ui/versions/AddonCheckUpdatesTask.java | 1 + .../hmcl/ui/versions/AddonUpdatesPage.java | 2 +- .../jackhuang/hmcl/addon/LocalAddonFile.java | 39 +++++++++++++++++-- .../org/jackhuang/hmcl/addon/RemoteAddon.java | 6 +-- .../hmcl/addon/mod/LocalModFile.java | 22 ++++------- .../CurseForgeRemoteAddonRepository.java | 10 +---- .../ModrinthRemoteAddonRepository.java | 10 +---- .../addon/resourcepack/ResourcePackFile.java | 6 +++ .../resourcepack/ResourcePackFolder.java | 7 ---- .../resourcepack/ResourcePackZipFile.java | 23 +++-------- 10 files changed, 60 insertions(+), 66 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java index 47597d34a63..b3b6fc8ba8f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java @@ -55,6 +55,7 @@ public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersi } } + if (candidate != null && candidate.targetVersion().equals(candidate.currentVersion())) return null; return candidate; }).setName(addon.getFileName()).setSignificance(TaskSignificance.MAJOR).withCounter("update.checking") ).toList(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java index b9c251715f6..c52936b810f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java @@ -206,7 +206,7 @@ public AddonUpdateObject(LocalAddonFile.AddonUpdate data) { fileName.set(data.localAddonFile().getFileName()); currentVersion.set(data.currentVersion().version()); targetVersion.set(data.targetVersion().version()); - switch (data.currentVersion().self().getType()) { + switch (data.currentVersion().source()) { case CURSEFORGE: source.set(i18n("addon.curseforge")); break; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index ab6c9d4aeca..8bcee62f883 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -24,9 +24,8 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; /// Sub-classes should implement `Comparable` @@ -35,6 +34,8 @@ public abstract class LocalAddonFile { protected LocalAddonFile() { } + public abstract RemoteAddonRepository.Type getType(); + public abstract Path getFile(); /// Without extension @@ -52,8 +53,38 @@ public boolean isDisabled() { public abstract void delete() throws IOException; + protected UpdateConditions getUpdateConditions() { + return UpdateConditions.NO_UPDATE; + } + @Nullable - public abstract AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException; + public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { + var conditions = getUpdateConditions(); + if (!conditions.canUpdate()) return null; + + RemoteAddonRepository repository = source.getRepoForType(getType()); + if (repository == null) return null; + Optional currentVersion = repository.getRemoteVersionByLocalFile(getFile()); + if (currentVersion.isEmpty()) return null; + + var stream = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().modid()) + .filter(version -> version.gameVersions().contains(gameVersion)); + if (conditions.predicates() != null) + for (var p : conditions.predicates()) { + stream = stream.filter(p); + } + List remoteVersions = stream.sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()).toList(); + if (remoteVersions.isEmpty()) return null; + return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), conditions.useRemoteFileName()); + } + + @SuppressWarnings("RedundantRecordConstructor") + protected record UpdateConditions(boolean canUpdate, boolean useRemoteFileName, @Nullable List> predicates) { + public static final UpdateConditions NO_UPDATE = new UpdateConditions(false, false, null); + + public UpdateConditions { + } + } public record AddonUpdate( LocalAddonFile localAddonFile, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java index e4b700a5284..6545a638d68 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java @@ -198,11 +198,7 @@ public interface IMod { Stream loadVersions(RemoteAddonRepository modRepository, DownloadProvider downloadProvider) throws IOException; } - public interface IVersion { - Source getType(); - } - - public record Version(IVersion self, String modid, String name, String version, String changelog, + public record Version(RemoteAddon.Source source, String modid, String name, String version, Instant datePublished, VersionType versionType, File file, List dependencies, List gameVersions, List loaders) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index f4bfafab115..a03d4d4f7d5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -21,9 +21,7 @@ import javafx.beans.property.SimpleBooleanProperty; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; -import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.RemoteAddonRepository; -import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.util.io.FileUtils; import java.io.IOException; @@ -96,6 +94,11 @@ protected void invalidated() { } } + @Override + public RemoteAddonRepository.Type getType() { + return RemoteAddonRepository.Type.MOD; + } + public ModManager getModManager() { return modManager; } @@ -195,19 +198,8 @@ public void delete() throws IOException { } @Override - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { - RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.MOD); - if (repository == null) return null; - Optional currentVersion = repository.getRemoteVersionByLocalFile(file); - if (currentVersion.isEmpty()) return null; - List remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().modid()) - .filter(version -> version.gameVersions().contains(gameVersion)) - .filter(version -> version.loaders().contains(getModLoaderType())) - .filter(version -> version.datePublished().compareTo(currentVersion.get().datePublished()) > 0) - .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) - .toList(); - if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), true); + protected UpdateConditions getUpdateConditions() { + return new UpdateConditions(true, true, List.of(v -> v.loaders().contains(getModLoaderType()))); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java index 3d12959d30f..a3755571376 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java @@ -441,7 +441,7 @@ public record LatestFile(int id, int gameId, int modId, boolean isAvailable, Str int releaseType, int fileStatus, List hashes, Instant fileDate, int fileLength, int downloadCount, String downloadUrl, List gameVersions, List dependencies, int alternateFileId, boolean isServerPack, - long fileFingerprint) implements RemoteAddon.IVersion { + long fileFingerprint) { @Override public String downloadUrl() { @@ -453,11 +453,6 @@ public String downloadUrl() { return downloadUrl; } - @Override - public RemoteAddon.Source getType() { - return RemoteAddon.Source.CURSEFORGE; - } - public RemoteAddon.Version toVersion() { RemoteAddon.VersionType versionType = switch (releaseType()) { case 1 -> RemoteAddon.VersionType.Release; @@ -467,11 +462,10 @@ public RemoteAddon.Version toVersion() { }; return new RemoteAddon.Version( - this, + RemoteAddon.Source.CURSEFORGE, Integer.toString(modId), displayName(), fileName(), - null, fileDate(), versionType, new RemoteAddon.File(Collections.emptyMap(), downloadUrl(), fileName()), diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java index 497b6925374..599b2463d57 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java @@ -367,7 +367,7 @@ public record ProjectVersion(String name, @SerializedName("version_number") Stri @SerializedName("author_id") String authorId, @SerializedName("date_published") Instant datePublished, int downloads, @SerializedName("changelog_url") String changelogUrl, - List files) implements RemoteAddon.IVersion { + List files) { private static final Map DEPENDENCY_TYPE = mapOf( pair("required", RemoteAddon.DependencyType.REQUIRED), pair("optional", RemoteAddon.DependencyType.OPTIONAL), @@ -375,11 +375,6 @@ public record ProjectVersion(String name, @SerializedName("version_number") Stri pair("incompatible", RemoteAddon.DependencyType.INCOMPATIBLE) ); - @Override - public RemoteAddon.Source getType() { - return RemoteAddon.Source.MODRINTH; - } - public Optional toVersion() { RemoteAddon.VersionType type; if ("release".equals(versionType)) { @@ -397,11 +392,10 @@ public Optional toVersion() { } return Optional.of(new RemoteAddon.Version( - this, + RemoteAddon.Source.MODRINTH, projectId, name, versionNumber, - changelog, datePublished, type, files.get(0).toFile(), diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java index ffa35e4c227..cd1b421d13c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java @@ -19,6 +19,7 @@ import javafx.scene.image.Image; import org.jackhuang.hmcl.addon.LocalAddonFile; +import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.addon.meta.PackMcMeta; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -61,6 +62,11 @@ protected ResourcePackFile(ResourcePackManager manager, Path file) { this.fileNameWithExtension = file.getFileName().toString(); } + @Override + public final RemoteAddonRepository.Type getType() { + return RemoteAddonRepository.Type.RESOURCE_PACK; + } + @Override public Path getFile() { return file; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFolder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFolder.java index 7267d04923d..e8187a2be5a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFolder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFolder.java @@ -18,8 +18,6 @@ package org.jackhuang.hmcl.addon.resourcepack; import javafx.scene.image.Image; -import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.meta.PackMcMeta; import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.Nullable; @@ -77,9 +75,4 @@ public PackMcMeta getMeta() { public void delete() throws IOException { FileUtils.deleteDirectory(file); } - - @Override - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) { - return null; - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java index 7b733539f41..ca8f844595c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java @@ -18,9 +18,6 @@ package org.jackhuang.hmcl.addon.resourcepack; import javafx.scene.image.Image; -import org.jackhuang.hmcl.download.DownloadProvider; -import org.jackhuang.hmcl.addon.RemoteAddon; -import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.addon.meta.PackMcMeta; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jetbrains.annotations.Nullable; @@ -29,13 +26,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; import static org.jackhuang.hmcl.util.logging.Logger.LOG; final class ResourcePackZipFile extends ResourcePackFile { + + private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(true, false, null); + private final PackMcMeta meta; private final @Nullable Image icon; @@ -90,18 +87,8 @@ public void delete() throws IOException { } @Override - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { - RemoteAddonRepository repository = source.getRepoForType(RemoteAddonRepository.Type.RESOURCE_PACK); - if (repository == null) return null; - Optional currentVersion = repository.getRemoteVersionByLocalFile(file); - if (currentVersion.isEmpty()) return null; - List remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().modid()) - .filter(version -> version.gameVersions().contains(gameVersion)) - .filter(version -> version.datePublished().compareTo(currentVersion.get().datePublished()) > 0) - .sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()) - .toList(); - if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), false); + protected UpdateConditions getUpdateConditions() { + return UPDATE_CONDITIONS; } } From 57a855fb9232a3cd4e06231ab311cfdfb526642b Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 15:26:14 +0800 Subject: [PATCH 02/17] update --- .../hmcl/ui/versions/AddonCheckUpdatesTask.java | 2 -- .../java/org/jackhuang/hmcl/addon/LocalAddonFile.java | 9 ++++----- .../java/org/jackhuang/hmcl/addon/mod/LocalModFile.java | 2 +- .../hmcl/addon/resourcepack/ResourcePackZipFile.java | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java index b3b6fc8ba8f..d2220b3e5ca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java @@ -31,11 +31,9 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class AddonCheckUpdatesTask extends Task> { - private final DownloadProvider downloadProvider; private final List> dependents; public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { - this.downloadProvider = downloadProvider; dependents = addons.stream().map(addon -> Task.supplyAsync(Schedulers.io(), () -> { LocalAddonFile.AddonUpdate candidate = null; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 8bcee62f883..9e719b52203 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -53,14 +53,15 @@ public boolean isDisabled() { public abstract void delete() throws IOException; + @Nullable protected UpdateConditions getUpdateConditions() { - return UpdateConditions.NO_UPDATE; + return null; } @Nullable public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { var conditions = getUpdateConditions(); - if (!conditions.canUpdate()) return null; + if (conditions == null) return null; RemoteAddonRepository repository = source.getRepoForType(getType()); if (repository == null) return null; @@ -79,9 +80,7 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe } @SuppressWarnings("RedundantRecordConstructor") - protected record UpdateConditions(boolean canUpdate, boolean useRemoteFileName, @Nullable List> predicates) { - public static final UpdateConditions NO_UPDATE = new UpdateConditions(false, false, null); - + protected record UpdateConditions(boolean useRemoteFileName, @Nullable List> predicates) { public UpdateConditions { } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index a03d4d4f7d5..186c4cfbc6e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -199,7 +199,7 @@ public void delete() throws IOException { @Override protected UpdateConditions getUpdateConditions() { - return new UpdateConditions(true, true, List.of(v -> v.loaders().contains(getModLoaderType()))); + return new UpdateConditions(true, List.of(v -> v.loaders().contains(getModLoaderType()))); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java index ca8f844595c..75661d7771b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java @@ -31,7 +31,7 @@ final class ResourcePackZipFile extends ResourcePackFile { - private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(true, false, null); + private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(false, null); private final PackMcMeta meta; private final @Nullable Image icon; From bbbf6d39edbf9858cec122041f90eb56db635919 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 15:54:53 +0800 Subject: [PATCH 03/17] update --- .../hmcl/ui/versions/AddonUpdatesPage.java | 23 +++++++------- .../jackhuang/hmcl/addon/LocalAddonFile.java | 10 ++++--- .../hmcl/addon/mod/LocalModFile.java | 2 +- .../resourcepack/ResourcePackManager.java | 30 +++++++++++++++++++ .../resourcepack/ResourcePackZipFile.java | 7 ++++- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java index c52936b810f..78217a6c106 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java @@ -293,18 +293,18 @@ public static class AddonUpdateTask extends Task { LocalAddonFile local = addon.localAddonFile(); RemoteAddon.Version remote = addon.targetVersion(); boolean isDisabled = local.isDisabled(); - String originalFileName = local.getFile().getFileName().toString(); + String fileName = remote.file().filename(); + if (isDisabled) + fileName = StringUtils.addSuffix(fileName, LocalAddonManager.DISABLED_EXTENSION); + String finalFileName = fileName; dependents.add(Task .runAsync(Schedulers.javafx(), () -> local.setOld(true)) .thenComposeAsync(() -> { - String fileName = addon.useRemoteFileName() ? remote.file().filename() : originalFileName; - if (isDisabled) - fileName = StringUtils.addSuffix(fileName, LocalAddonManager.DISABLED_EXTENSION); var task = new FileDownloadTask( remote.file().url(), - addonDirectory.resolve(fileName) + addonDirectory.resolve(finalFileName) ); task.setName(remote.name()); @@ -317,11 +317,14 @@ public static class AddonUpdateTask extends Task { if (isDisabled) local.markDisabled(); failedAddons.add(local); - } else if (!local.keepOldFiles()) { - try { - local.delete(); - } catch (IOException e) { - LOG.warning("Failed to delete outdated addon: " + local.getFile(), e); + } else { + local.onUpdated(finalFileName); + if (!local.keepOldFiles()) { + try { + local.delete(); + } catch (IOException e) { + LOG.warning("Failed to delete outdated addon: " + local.getFile(), e); + } } } }) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 9e719b52203..d36b9d75d75 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -76,11 +76,14 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe } List remoteVersions = stream.sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()).toList(); if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0), conditions.useRemoteFileName()); + return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0)); + } + + public void onUpdated(String newFileNameWithExt) { } @SuppressWarnings("RedundantRecordConstructor") - protected record UpdateConditions(boolean useRemoteFileName, @Nullable List> predicates) { + protected record UpdateConditions(@Nullable List> predicates) { public UpdateConditions { } } @@ -88,8 +91,7 @@ protected record UpdateConditions(boolean useRemoteFileName, @Nullable List v.loaders().contains(getModLoaderType()))); + return new UpdateConditions(List.of(v -> v.loaders().contains(getModLoaderType()))); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java index 5799909e92f..2c7df7a1b87 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java @@ -506,6 +506,36 @@ public boolean isEnabled(ResourcePackFile resourcePack) { } } + public boolean rename(String oldName, String newName) { + String oldNameNewFormat = "file/" + oldName; + boolean modified = false; + + lock.lock(); + try { + Map options = loadOptions(); + List optPacks = new ArrayList<>(deserializePackList(options.get("resourcePacks"))); + List optIncompatiblePacks = new ArrayList<>(deserializePackList(options.get("incompatibleResourcePacks"))); + for (var l : List.of(optPacks, optIncompatiblePacks)) { + var it = l.listIterator(); + while (it.hasNext()) { + var next = it.next(); + if (oldName.equals(next) || oldNameNewFormat.equals(next)) { + it.set(newName); + modified = true; + } + } + } + if (modified) { + options.put("resourcePacks", serializePackList(optPacks)); + options.put("incompatibleResourcePacks", serializePackList(optIncompatiblePacks)); + saveOptions(options); + } + return modified; + } finally { + lock.unlock(); + } + } + public Stream> arePacksEnabled(Stream resourcePacks) { lock.lock(); try { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java index 75661d7771b..60e74035f2e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java @@ -31,7 +31,7 @@ final class ResourcePackZipFile extends ResourcePackFile { - private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(false, null); + private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(null); private final PackMcMeta meta; private final @Nullable Image icon; @@ -90,5 +90,10 @@ public void delete() throws IOException { protected UpdateConditions getUpdateConditions() { return UPDATE_CONDITIONS; } + + @Override + public void onUpdated(String newFileNameWithExt) { + manager.rename(getFileNameWithExtension(), newFileNameWithExt); + } } From 813cd0e851c2d7be0d791c2918d3bf2ed1d88d9b Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 15:56:13 +0800 Subject: [PATCH 04/17] update --- .../org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java | 6 +++--- .../hmcl/addon/resourcepack/ResourcePackZipFile.java | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java index 78217a6c106..711fe5c9521 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java @@ -296,7 +296,7 @@ public static class AddonUpdateTask extends Task { String fileName = remote.file().filename(); if (isDisabled) fileName = StringUtils.addSuffix(fileName, LocalAddonManager.DISABLED_EXTENSION); - String finalFileName = fileName; + String newFileName = fileName; dependents.add(Task .runAsync(Schedulers.javafx(), () -> local.setOld(true)) @@ -304,7 +304,7 @@ public static class AddonUpdateTask extends Task { var task = new FileDownloadTask( remote.file().url(), - addonDirectory.resolve(finalFileName) + addonDirectory.resolve(newFileName) ); task.setName(remote.name()); @@ -318,7 +318,7 @@ public static class AddonUpdateTask extends Task { local.markDisabled(); failedAddons.add(local); } else { - local.onUpdated(finalFileName); + local.onUpdated(newFileName); if (!local.keepOldFiles()) { try { local.delete(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java index 60e74035f2e..b0e44a599a7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java @@ -93,6 +93,7 @@ protected UpdateConditions getUpdateConditions() { @Override public void onUpdated(String newFileNameWithExt) { + super.onUpdated(newFileNameWithExt); manager.rename(getFileNameWithExtension(), newFileNameWithExt); } } From f46c935ea7dc8488719b234c450ca50e2e2d295d Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 17:45:22 +0800 Subject: [PATCH 05/17] update --- .../hmcl/ui/versions/AddonUpdatesPage.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java index 711fe5c9521..ade456e35bc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonUpdatesPage.java @@ -300,17 +300,9 @@ public static class AddonUpdateTask extends Task { dependents.add(Task .runAsync(Schedulers.javafx(), () -> local.setOld(true)) - .thenComposeAsync(() -> { - - var task = new FileDownloadTask( - remote.file().url(), - addonDirectory.resolve(newFileName) - ); - - task.setName(remote.name()); - return task; - }) - .whenComplete(Schedulers.javafx(), exception -> { + .thenComposeAsync(() -> + new FileDownloadTask(remote.file().url(), addonDirectory.resolve(newFileName)).setName(remote.name()) + ).whenComplete(Schedulers.javafx(), exception -> { if (exception != null) { // restore state if failed local.setOld(false); From e4f1dab28484deff26788d9322b7d709de7198d4 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 17:47:39 +0800 Subject: [PATCH 06/17] update --- .../org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java | 4 ++-- .../main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java | 2 +- .../org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java index d2220b3e5ca..ef3389602e5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/AddonCheckUpdatesTask.java @@ -30,10 +30,10 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AddonCheckUpdatesTask extends Task> { +public class AddonCheckUpdatesTask extends Task> { private final List> dependents; - public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { + public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { dependents = addons.stream().map(addon -> Task.supplyAsync(Schedulers.io(), () -> { LocalAddonFile.AddonUpdate candidate = null; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index 7ea215d8001..1212200804a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -245,7 +245,7 @@ public void checkUpdates(Collection mods) { Runnable action = () -> Controllers.taskDialog(Task .composeAsync(() -> { Optional gameVersion = repository.getGameVersion(instanceId); - return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, mods)).orElse(null); + return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, mods)).orElse(null); }) .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception instanceof CancellationException) return; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java index d8688c9c05a..f618ffc9413 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ResourcePackListPage.java @@ -232,7 +232,7 @@ public void checkUpdates(Collection resourcePacks) { Runnable action = () -> Controllers.taskDialog(Task .composeAsync(() -> { Optional gameVersion = repository.getGameVersion(instanceId); - return gameVersion.map(g -> new AddonCheckUpdatesTask<>(DownloadProviders.getDownloadProvider(), g, resourcePacks)).orElse(null); + return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, resourcePacks)).orElse(null); }) .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception != null || result == null) { From 5b0f05696b7b0b9604cca6b0110016415ffd8ee1 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 17:54:21 +0800 Subject: [PATCH 07/17] update --- .../java/org/jackhuang/hmcl/ui/versions/ModListPage.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java index 1212200804a..8bb0e0e2e27 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPage.java @@ -245,12 +245,18 @@ public void checkUpdates(Collection mods) { Runnable action = () -> Controllers.taskDialog(Task .composeAsync(() -> { Optional gameVersion = repository.getGameVersion(instanceId); - return gameVersion.map(g -> new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), g, mods)).orElse(null); + if (gameVersion.isPresent()) { + return new AddonCheckUpdatesTask(DownloadProviders.getDownloadProvider(), gameVersion.get(), mods); + } else { + LOG.warning("Failed to check for updates, due to unable to get instance game version"); + return null; + } }) .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception instanceof CancellationException) return; if (exception != null || result == null) { Controllers.dialog(i18n("addon.check_update.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR); + if (exception != null) LOG.warning("Failed to check for updates", exception); } else if (result.isEmpty()) { Controllers.dialog(i18n("addon.check_update.empty")); } else { From 19c93fda2e21d8639954ac75a8ed2faa58375d53 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Wed, 29 Jul 2026 21:52:24 +0800 Subject: [PATCH 08/17] update --- .../jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java index 5e465d3e607..45f7fa9659e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java @@ -509,6 +509,7 @@ public boolean isEnabled(ResourcePackFile resourcePack) { public boolean rename(String oldName, String newName) { String oldNameNewFormat = "file/" + oldName; + String newNameNewFormat = "file/" + newName; boolean modified = false; lock.lock(); @@ -521,7 +522,7 @@ public boolean rename(String oldName, String newName) { while (it.hasNext()) { var next = it.next(); if (oldName.equals(next) || oldNameNewFormat.equals(next)) { - it.set(newName); + it.set(supportsNewOptionsFormat ? newNameNewFormat : newName); modified = true; } } From 3c18e2c4ed30f2181d52e03440f1eb96a4b01fa5 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Fri, 31 Jul 2026 21:05:01 +0800 Subject: [PATCH 09/17] addon update channel --- .../jackhuang/hmcl/setting/LauncherSettings.java | 9 +++++++++ .../hmcl/ui/instances/AddonCheckUpdatesTask.java | 3 ++- .../hmcl/ui/main/DownloadSettingsPage.java | 12 ++++++++---- .../main/resources/assets/lang/I18N.properties | 2 ++ .../resources/assets/lang/I18N_zh.properties | 2 ++ .../resources/assets/lang/I18N_zh_CN.properties | 2 ++ .../org/jackhuang/hmcl/addon/LocalAddonFile.java | 4 +++- .../ModrinthRemoteAddonRepository.java | 16 ++++++---------- 8 files changed, 34 insertions(+), 16 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java index f2802244981..7f829928cf7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java @@ -541,6 +541,15 @@ public StringProperty defaultAddonSourceProperty() { return defaultAddonSource; } + /// Whether to update addons to preview versions + @SerializedName("updateAddonsToPreview") + private final BooleanProperty updateAddonsToPreview = new SimpleBooleanProperty(true); + + /// Returns whether to update addons to preview versions property. + public BooleanProperty updateAddonsToPreviewProperty() { + return updateAddonsToPreview; + } + /// Whether proxy authentication is enabled. @SerializedName("hasProxyAuth") private final BooleanProperty hasProxyAuth = new SimpleBooleanProperty(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java index b731dcd845b..5e546acbfcf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java @@ -20,6 +20,7 @@ import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.RemoteAddon; +import org.jackhuang.hmcl.setting.SettingsManager; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -40,7 +41,7 @@ public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersi for (RemoteAddon.Source source : RemoteAddon.Source.values()) { LocalAddonFile.AddonUpdate update = null; try { - update = addon.checkUpdates(downloadProvider, gameVersion, source); + update = addon.checkUpdates(downloadProvider, gameVersion, source, SettingsManager.settings().updateAddonsToPreviewProperty().get()); } catch (IOException e) { LOG.warning(String.format("Cannot check update for addon %s.", addon.getFileName()), e); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index ee4ad559c9a..29b05c5e67d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -62,8 +62,8 @@ public DownloadSettingsPage() { getChildren().setAll(scrollPane); { - var downloadSource = new ComponentList(); - downloadSource.getStyleClass().add("card-non-transparent"); + var gameContent = new ComponentList(); + gameContent.getStyleClass().add("card-non-transparent"); { Function converter = source -> switch (source) { case DEFAULT -> i18n("settings.launcher.download_source.auto"); @@ -99,10 +99,14 @@ public DownloadSettingsPage() { defaultAddonSourcePane.setItems("modrinth", "curseforge"); defaultAddonSourcePane.valueProperty().bindBidirectional(settings().defaultAddonSourceProperty()); - downloadSource.getContent().setAll(versionListSourcePane, downloadSourcePane, defaultAddonSourcePane); + var updateAddonsToPreviewButton = new LineToggleButton(); + updateAddonsToPreviewButton.setTitle(i18n("settings.launcher.update_addons_to_preview")); + updateAddonsToPreviewButton.selectedProperty().bindBidirectional(settings().updateAddonsToPreviewProperty()); + + gameContent.getContent().setAll(versionListSourcePane, downloadSourcePane, defaultAddonSourcePane, updateAddonsToPreviewButton); } - content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.download_source")), downloadSource); + content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.game_content")), gameContent); } { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 6ee719bc055..d5481cc5b51 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1619,6 +1619,7 @@ settings.launcher.font.anti_aliasing.auto=Auto settings.launcher.font.anti_aliasing.gray=Grayscale settings.launcher.font.anti_aliasing.lcd=Sub-pixel settings.launcher.fonts=Fonts +settings.launcher.game_content=Game Content settings.launcher.general=General settings.launcher.language=Language settings.launcher.launcher_log.export=Export Launcher Logs @@ -1665,6 +1666,7 @@ settings.launcher.theme_color_type.default=Default settings.launcher.theme_color_type.system=Follow System settings.launcher.title_transparent=Transparent Titlebar settings.launcher.turn_off_animations=Disable Animation +settings.launcher.update_addons_to_preview=Update Addons to Preview Versions settings.launcher.version_list_source=Version List settings.launcher.window_transparent=Transparent Window settings.launcher.background.settings.opacity=Opacity diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index f4021b9b3be..1f2cc55cef0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1420,6 +1420,7 @@ settings.launcher.font.anti_aliasing.auto=自動 settings.launcher.font.anti_aliasing.gray=灰階 settings.launcher.font.anti_aliasing.lcd=子像素 settings.launcher.fonts=字體 +settings.launcher.game_content=遊戲內容 settings.launcher.general=一般 settings.launcher.language=語言 settings.launcher.launcher_log.export=匯出啟動器日誌 @@ -1466,6 +1467,7 @@ settings.launcher.theme_color_type.default=預設 settings.launcher.theme_color_type.system=跟隨系統 settings.launcher.title_transparent=標題欄透明 settings.launcher.turn_off_animations=關閉動畫 +settings.launcher.update_addons_to_preview=更新附加內容到預覽版本 settings.launcher.version_list_source=版本清單來源 settings.launcher.window_transparent=視窗透明 settings.launcher.background.settings.opacity=不透明度 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 848051d09fb..916cc1c44c3 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1420,6 +1420,7 @@ settings.launcher.font.anti_aliasing.auto=自动 settings.launcher.font.anti_aliasing.gray=灰度 settings.launcher.font.anti_aliasing.lcd=子像素 settings.launcher.fonts=字体 +settings.launcher.game_content=游戏内容 settings.launcher.general=通用 settings.launcher.language=语言 settings.launcher.launcher_log.export=导出启动器日志 @@ -1466,6 +1467,7 @@ settings.launcher.theme_color_type.default=默认 settings.launcher.theme_color_type.system=跟随系统 settings.launcher.title_transparent=标题栏透明 settings.launcher.turn_off_animations=关闭动画 +settings.launcher.update_addons_to_preview=更新附加内容到预览版本 settings.launcher.version_list_source=版本列表源 settings.launcher.window_transparent=窗口透明 settings.launcher.background.settings.opacity=不透明度 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index d36b9d75d75..291deb0bb8e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -59,7 +59,7 @@ protected UpdateConditions getUpdateConditions() { } @Nullable - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { + public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source, boolean updateToPreview) throws IOException { var conditions = getUpdateConditions(); if (conditions == null) return null; @@ -70,6 +70,8 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe var stream = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().modid()) .filter(version -> version.gameVersions().contains(gameVersion)); + if (!updateToPreview) + stream = stream.filter(version -> version.versionType() == RemoteAddon.VersionType.Release); if (conditions.predicates() != null) for (var p : conditions.predicates()) { stream = stream.filter(p); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java index 599b2463d57..bb2567dbab1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/ModrinthRemoteAddonRepository.java @@ -376,16 +376,12 @@ public record ProjectVersion(String name, @SerializedName("version_number") Stri ); public Optional toVersion() { - RemoteAddon.VersionType type; - if ("release".equals(versionType)) { - type = RemoteAddon.VersionType.Release; - } else if ("beta".equals(versionType)) { - type = RemoteAddon.VersionType.Beta; - } else if ("alpha".equals(versionType)) { - type = RemoteAddon.VersionType.Alpha; - } else { - type = RemoteAddon.VersionType.Release; - } + RemoteAddon.VersionType type = switch (versionType) { + case "release" -> RemoteAddon.VersionType.Release; + case "beta" -> RemoteAddon.VersionType.Beta; + case "alpha" -> RemoteAddon.VersionType.Alpha; + default -> RemoteAddon.VersionType.Release; + }; if (files.isEmpty()) { return Optional.empty(); From 96435dabe407a14d97c8e5b5d67b4545b3853d72 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 1 Aug 2026 13:52:34 +0800 Subject: [PATCH 10/17] version type --- .../hmcl/ui/instances/AddonUpdatesPage.java | 53 +++++-------------- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + 4 files changed, 15 insertions(+), 41 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java index dd19fb09779..48167d0b2da 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java @@ -94,6 +94,9 @@ public AddonUpdatesPage(LocalAddonManager localAddonManager, List targetVersionTypeColumn = new TableColumn<>(i18n("addon.version_type")); + setupCellValueFactory(targetVersionTypeColumn, AddonUpdateObject::targetVersionTypeProperty); + TableColumn sourceColumn = new TableColumn<>(i18n("addon.check_update.source")); setupCellValueFactory(sourceColumn, AddonUpdateObject::sourceProperty); @@ -102,7 +105,7 @@ public AddonUpdatesPage(LocalAddonManager localAddonManager, List table = new TableView<>(objects); table.setEditable(true); - table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, sourceColumn); + table.getColumns().setAll(enabledColumn, fileNameColumn, currentVersionColumn, targetVersionColumn, targetVersionTypeColumn, sourceColumn); setMargin(table, new Insets(10, 10, 5, 10)); setCenter(table); @@ -197,6 +200,7 @@ private static final class AddonUpdateObject { final StringProperty fileName = new SimpleStringProperty(); final StringProperty currentVersion = new SimpleStringProperty(); final StringProperty targetVersion = new SimpleStringProperty(); + final StringProperty targetVersionType = new SimpleStringProperty(); final StringProperty source = new SimpleStringProperty(); public AddonUpdateObject(LocalAddonFile.AddonUpdate data) { @@ -206,13 +210,11 @@ public AddonUpdateObject(LocalAddonFile.AddonUpdate data) { fileName.set(data.localAddonFile().getFileName()); currentVersion.set(data.currentVersion().version()); targetVersion.set(data.targetVersion().version()); - switch (data.currentVersion().source()) { - case CURSEFORGE: - source.set(i18n("addon.curseforge")); - break; - case MODRINTH: - source.set(i18n("addon.modrinth")); - } + targetVersionType.set(data.targetVersion().versionType().name()); + source.set(switch (data.currentVersion().source()) { + case CURSEFORGE -> i18n("addon.curseforge"); + case MODRINTH -> i18n("addon.modrinth"); + }); } public LocalAddonFile.AddonUpdate getData() { @@ -227,57 +229,26 @@ public BooleanProperty enabledProperty() { return enabled; } - public void setEnabled(boolean enabled) { - this.enabled.set(enabled); - } - - public String getFileName() { - return fileName.get(); - } - public StringProperty fileNameProperty() { return fileName; } - public void setFileName(String fileName) { - this.fileName.set(fileName); - } - - public String getCurrentVersion() { - return currentVersion.get(); - } - public StringProperty currentVersionProperty() { return currentVersion; } - public void setCurrentVersion(String currentVersion) { - this.currentVersion.set(currentVersion); - } - - public String getTargetVersion() { - return targetVersion.get(); - } - public StringProperty targetVersionProperty() { return targetVersion; } - public void setTargetVersion(String targetVersion) { - this.targetVersion.set(targetVersion); - } - - public String getSource() { - return source.get(); + public StringProperty targetVersionTypeProperty() { + return targetVersionType; } public StringProperty sourceProperty() { return source; } - public void setSource(String source) { - this.source.set(source); - } } public static class AddonUpdateTask extends Task { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index d5481cc5b51..4b0b6a0678b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -199,6 +199,7 @@ addon.dependency.broken=Broken Dependencies (This addon existed before, but it d addon.download.title.release=Minecraft %s addon.download.title.snapshot=Minecraft %s (Snapshots) addon.modrinth=Modrinth +addon.version_type=Type archive.author=Author(s) archive.date=Publish Date diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 1f2cc55cef0..95526820092 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -197,6 +197,7 @@ addon.dependency.broken=損壞的相依內容 (該相依內容曾經存在於附 addon.download.title.release=Minecraft %s addon.download.title.snapshot=Minecraft %s (快照) addon.modrinth=Modrinth +addon.version_type=版本類型 archive.author=作者 archive.date=發布日期 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 916cc1c44c3..1a6543f2c17 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -199,6 +199,7 @@ addon.dependency.broken=损坏的前置内容 (该前置内容曾经在该附加 addon.download.title.release=Minecraft %s addon.download.title.snapshot=Minecraft %s (快照) addon.modrinth=Modrinth +addon.version_type=版本类型 archive.author=作者 archive.date=发布日期 From 887d021cb30b43a6baf7c3e737227f91973cdd52 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sat, 1 Aug 2026 15:39:15 +0800 Subject: [PATCH 11/17] update i18n --- HMCL/src/main/resources/assets/lang/I18N.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 2 +- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 4b0b6a0678b..d54fec588af 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -1667,7 +1667,7 @@ settings.launcher.theme_color_type.default=Default settings.launcher.theme_color_type.system=Follow System settings.launcher.title_transparent=Transparent Titlebar settings.launcher.turn_off_animations=Disable Animation -settings.launcher.update_addons_to_preview=Update Addons to Preview Versions +settings.launcher.update_addons_to_preview=Update Addons to Alpha/Beta Versions settings.launcher.version_list_source=Version List settings.launcher.window_transparent=Transparent Window settings.launcher.background.settings.opacity=Opacity diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 95526820092..c6fdcd9f1d8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -1468,7 +1468,7 @@ settings.launcher.theme_color_type.default=預設 settings.launcher.theme_color_type.system=跟隨系統 settings.launcher.title_transparent=標題欄透明 settings.launcher.turn_off_animations=關閉動畫 -settings.launcher.update_addons_to_preview=更新附加內容到預覽版本 +settings.launcher.update_addons_to_preview=更新附加內容到 Alpha/Beta 版本 settings.launcher.version_list_source=版本清單來源 settings.launcher.window_transparent=視窗透明 settings.launcher.background.settings.opacity=不透明度 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 1a6543f2c17..dd1ac456935 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1468,7 +1468,7 @@ settings.launcher.theme_color_type.default=默认 settings.launcher.theme_color_type.system=跟随系统 settings.launcher.title_transparent=标题栏透明 settings.launcher.turn_off_animations=关闭动画 -settings.launcher.update_addons_to_preview=更新附加内容到预览版本 +settings.launcher.update_addons_to_preview=更新附加内容到测试版本 settings.launcher.version_list_source=版本列表源 settings.launcher.window_transparent=窗口透明 settings.launcher.background.settings.opacity=不透明度 From bc0aa10fedeca22ec587f23f722e59a5a00ec1ea Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 2 Aug 2026 10:42:19 +0800 Subject: [PATCH 12/17] update --- .../src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 291deb0bb8e..16e9cd540cb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -68,7 +68,7 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe Optional currentVersion = repository.getRemoteVersionByLocalFile(getFile()); if (currentVersion.isEmpty()) return null; - var stream = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().modid()) + var stream = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().projectId()) .filter(version -> version.gameVersions().contains(gameVersion)); if (!updateToPreview) stream = stream.filter(version -> version.versionType() == RemoteAddon.VersionType.Release); From 1fbb760b0f8cef4265eb7305600e69bc9774f61f Mon Sep 17 00:00:00 2001 From: ToobLac Date: Sun, 2 Aug 2026 20:48:58 +0800 Subject: [PATCH 13/17] update --- .../java/org/jackhuang/hmcl/addon/LocalAddonFile.java | 6 ++---- .../java/org/jackhuang/hmcl/addon/mod/LocalModFile.java | 9 ++------- .../hmcl/addon/resourcepack/ResourcePackFile.java | 6 ------ .../hmcl/addon/resourcepack/ResourcePackZipFile.java | 3 ++- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 16e9cd540cb..90443a38518 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -34,8 +34,6 @@ public abstract class LocalAddonFile { protected LocalAddonFile() { } - public abstract RemoteAddonRepository.Type getType(); - public abstract Path getFile(); /// Without extension @@ -63,7 +61,7 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe var conditions = getUpdateConditions(); if (conditions == null) return null; - RemoteAddonRepository repository = source.getRepoForType(getType()); + RemoteAddonRepository repository = source.getRepoForType(conditions.type()); if (repository == null) return null; Optional currentVersion = repository.getRemoteVersionByLocalFile(getFile()); if (currentVersion.isEmpty()) return null; @@ -85,7 +83,7 @@ public void onUpdated(String newFileNameWithExt) { } @SuppressWarnings("RedundantRecordConstructor") - protected record UpdateConditions(@Nullable List> predicates) { + protected record UpdateConditions(RemoteAddon.Type type, @Nullable List> predicates) { public UpdateConditions { } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index eeecb3fbe3f..97994df2887 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -21,7 +21,7 @@ import javafx.beans.property.SimpleBooleanProperty; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; -import org.jackhuang.hmcl.addon.RemoteAddonRepository; +import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.util.io.FileUtils; import java.io.IOException; @@ -94,11 +94,6 @@ protected void invalidated() { } } - @Override - public RemoteAddonRepository.Type getType() { - return RemoteAddonRepository.Type.MOD; - } - public ModManager getModManager() { return modManager; } @@ -199,7 +194,7 @@ public void delete() throws IOException { @Override protected UpdateConditions getUpdateConditions() { - return new UpdateConditions(List.of(v -> v.loaders().contains(getModLoaderType()))); + return new UpdateConditions(RemoteAddon.Type.MOD, List.of(v -> v.loaders().contains(getModLoaderType()))); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java index cd1b421d13c..ffa35e4c227 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackFile.java @@ -19,7 +19,6 @@ import javafx.scene.image.Image; import org.jackhuang.hmcl.addon.LocalAddonFile; -import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.addon.meta.PackMcMeta; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; @@ -62,11 +61,6 @@ protected ResourcePackFile(ResourcePackManager manager, Path file) { this.fileNameWithExtension = file.getFileName().toString(); } - @Override - public final RemoteAddonRepository.Type getType() { - return RemoteAddonRepository.Type.RESOURCE_PACK; - } - @Override public Path getFile() { return file; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java index b0e44a599a7..3cde605daa7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.addon.resourcepack; import javafx.scene.image.Image; +import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.meta.PackMcMeta; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jetbrains.annotations.Nullable; @@ -31,7 +32,7 @@ final class ResourcePackZipFile extends ResourcePackFile { - private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(null); + private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(RemoteAddon.Type.RESOURCE_PACK, null); private final PackMcMeta meta; private final @Nullable Image icon; From eb620a0b05b1ab52959169f8f793fa3978f08dd3 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 6 Aug 2026 11:09:10 +0800 Subject: [PATCH 14/17] update --- .../hmcl/setting/LauncherSettings.java | 10 ++-- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 59 +++++++++---------- .../ui/instances/AddonCheckUpdatesTask.java | 50 ++++++++++------ .../hmcl/ui/instances/AddonUpdatesPage.java | 48 ++++++++++----- .../hmcl/ui/instances/ModListPage.java | 4 +- .../ui/instances/ResourcePackListPage.java | 4 +- .../hmcl/ui/main/DownloadSettingsPage.java | 4 +- .../resources/assets/lang/I18N.properties | 3 +- .../resources/assets/lang/I18N_zh.properties | 3 +- .../assets/lang/I18N_zh_CN.properties | 3 +- .../org/jackhuang/hmcl/addon/AddonUpdate.java | 37 ++++++++++++ .../jackhuang/hmcl/addon/LocalAddonFile.java | 38 ++++++------ .../hmcl/addon/mod/LocalModFile.java | 5 +- .../resourcepack/ResourcePackZipFile.java | 5 +- 14 files changed, 170 insertions(+), 103 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonUpdate.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java index 7f829928cf7..71caaaf61cd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/LauncherSettings.java @@ -542,12 +542,12 @@ public StringProperty defaultAddonSourceProperty() { } /// Whether to update addons to preview versions - @SerializedName("updateAddonsToPreview") - private final BooleanProperty updateAddonsToPreview = new SimpleBooleanProperty(true); + @SerializedName("defaultUpdateAddonsToPreview") + private final BooleanProperty defaultUpdateAddonsToPreview = new SimpleBooleanProperty(true); - /// Returns whether to update addons to preview versions property. - public BooleanProperty updateAddonsToPreviewProperty() { - return updateAddonsToPreview; + /// Returns whether to update addons to preview versions property by default. + public BooleanProperty defaultUpdateAddonsToPreviewProperty() { + return defaultUpdateAddonsToPreview; } /// Whether proxy authentication is enabled. diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index d006a67656a..83b15fbdc0d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -62,6 +62,7 @@ import javafx.util.Callback; import javafx.util.Duration; import javafx.util.StringConverter; +import javafx.util.Subscription; import org.glavo.url.WebURL; import org.jackhuang.hmcl.setting.StyleSheets; import org.jackhuang.hmcl.task.CacheFileTask; @@ -1069,7 +1070,7 @@ public static void unbindWindowsSize(JFXComboBox comboBox, IntegerProper heightProperty.removeListener(binding); } - public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) { + public static List bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) { int itemCount = children.length; int childSelectedCount = 0; for (BooleanProperty child : children) { @@ -1079,49 +1080,45 @@ public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... allEnabled.set(childSelectedCount == itemCount); - class Listener implements InvalidationListener { + class AllListener { private int childSelectedCount; private boolean updating = false; + private final List subscriptions = new ArrayList<>(children.length + 1); - public Listener(int childSelectedCount) { + public AllListener(int childSelectedCount) { this.childSelectedCount = childSelectedCount; } - @Override - public void invalidated(Observable observable) { - if (updating) - return; - - updating = true; - try { - boolean value = ((BooleanProperty) observable).get(); - - if (observable == allEnabled) { + { + subscriptions.add(allEnabled.subscribe(() -> { + if (updating) return; + updating = true; + try { + boolean value = allEnabled.get(); for (BooleanProperty child : children) { - child.setValue(value); + child.set(value); } childSelectedCount = value ? itemCount : 0; - } else { - if (value) - childSelectedCount++; - else - childSelectedCount--; - - allEnabled.set(childSelectedCount == itemCount); + } finally { + updating = false; } - } finally { - updating = false; + })); + for (var child : children) { + subscriptions.add(child.subscribe(() -> { + if (updating) return; + updating = true; + try { + if (child.get()) childSelectedCount++; + else childSelectedCount--; + allEnabled.set(childSelectedCount == itemCount); + } finally { + updating = false; + } + })); } } } - - InvalidationListener listener = new Listener(childSelectedCount); - - WeakInvalidationListener weakListener = new WeakInvalidationListener(listener); - allEnabled.addListener(listener); - for (BooleanProperty child : children) { - child.addListener(weakListener); - } + return new AllListener(childSelectedCount).subscriptions; } public static void setIcon(Stage stage) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java index 5e546acbfcf..8feb885b104 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonCheckUpdatesTask.java @@ -17,45 +17,54 @@ */ package org.jackhuang.hmcl.ui.instances; +import org.jackhuang.hmcl.addon.AddonUpdate; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.RemoteAddon; -import org.jackhuang.hmcl.setting.SettingsManager; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Pair; +import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AddonCheckUpdatesTask extends Task> { - private final List> dependents; +public class AddonCheckUpdatesTask extends Task { + private final List>> dependents; public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { dependents = addons.stream().map(addon -> Task.supplyAsync(Schedulers.io(), () -> { - LocalAddonFile.AddonUpdate candidate = null; + AddonUpdate candidate = null; + AddonUpdate candidateRelease = null; for (RemoteAddon.Source source : RemoteAddon.Source.values()) { - LocalAddonFile.AddonUpdate update = null; + AddonUpdate update = null; + AddonUpdate updateRelease = null; try { - update = addon.checkUpdates(downloadProvider, gameVersion, source, SettingsManager.settings().updateAddonsToPreviewProperty().get()); + var u = addon.checkUpdates(downloadProvider, gameVersion, source); + if (u != null) { + update = u.key(); + updateRelease = u.value(); + } } catch (IOException e) { LOG.warning(String.format("Cannot check update for addon %s.", addon.getFileName()), e); } - if (update == null) { - continue; - } - - if (candidate == null || candidate.targetVersion().datePublished().isBefore(update.targetVersion().datePublished())) { + if (update == null) continue; + if (candidate == null || candidate.targetVersion().datePublished().isBefore(update.targetVersion().datePublished())) candidate = update; - } + + if (updateRelease == null) continue; + if (candidateRelease == null || candidateRelease.targetVersion().datePublished().isBefore(updateRelease.targetVersion().datePublished())) + candidateRelease = updateRelease; } - if (candidate != null && candidate.targetVersion().equals(candidate.currentVersion())) return null; - return candidate; + if (candidate == null) return null; // If there's no candidate for all channels, then no candidate for release channel + return Pair.pair(candidate, candidateRelease); }).setName(addon.getFileName()).setSignificance(TaskSignificance.MAJOR).withCounter("update.checking") ).toList(); @@ -85,8 +94,15 @@ public boolean isRelyingOnDependents() { @Override public void execute() throws Exception { - setResult(dependents.stream() - .map(Task::getResult) - .filter(Objects::nonNull).toList()); + List commonUpdates = new ArrayList<>(), releaseUpdates = new ArrayList<>(); + dependents.stream().map(Task::getResult).filter(Objects::nonNull) + .forEachOrdered(pair -> { + commonUpdates.add(pair.key()); + if (pair.value() != null) releaseUpdates.add(pair.value()); + }); + setResult(new Result(List.copyOf(commonUpdates), List.copyOf(releaseUpdates))); + } + + public record Result(List commonUpdates, List releaseUpdates) { } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java index 48167d0b2da..88d2ea603f3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java @@ -29,9 +29,12 @@ import javafx.scene.control.TableView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; +import javafx.util.Subscription; +import org.jackhuang.hmcl.addon.AddonUpdate; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; import org.jackhuang.hmcl.addon.RemoteAddon; +import org.jackhuang.hmcl.setting.SettingsManager; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -41,6 +44,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.PageCloseEvent; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jackhuang.hmcl.util.Lazy; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.CSVTable; @@ -50,9 +54,7 @@ import java.nio.file.Paths; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -60,15 +62,15 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AddonUpdatesPage extends BorderPane implements DecoratorPage { +public class AddonUpdatesPage extends BorderPane implements DecoratorPage { private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("addon.check_update"))); - private final LocalAddonManager localAddonManager; + private final Path localAddonDirectory; private final ObservableList objects; @SuppressWarnings("unchecked") - public AddonUpdatesPage(LocalAddonManager localAddonManager, List updates) { - this.localAddonManager = localAddonManager; + public AddonUpdatesPage(Path localAddonDirectory, AddonCheckUpdatesTask.Result updates) { + this.localAddonDirectory = localAddonDirectory; getStyleClass().add("gray-background"); @@ -100,8 +102,19 @@ public AddonUpdatesPage(LocalAddonManager localAddonManager, List sourceColumn = new TableColumn<>(i18n("addon.check_update.source")); setupCellValueFactory(sourceColumn, AddonUpdateObject::sourceProperty); - objects = FXCollections.observableList(updates.stream().map(AddonUpdateObject::new).collect(Collectors.toList())); - FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new)); + var common = new Lazy<>(() -> updates.commonUpdates().stream().map(AddonUpdateObject::new).toList()); + var release = new Lazy<>(() -> updates.releaseUpdates().stream().map(AddonUpdateObject::new).toList()); + + objects = FXCollections.observableArrayList(); + List subscriptions = new ArrayList<>(); + BooleanProperty showPreview = new SimpleBooleanProperty(SettingsManager.settings().defaultUpdateAddonsToPreviewProperty().get()); + FXUtils.onChangeAndOperate(showPreview, preview -> { + if (preview) objects.setAll(common.get()); + else objects.setAll(release.get()); + subscriptions.forEach(Subscription::unsubscribe); + subscriptions.clear(); + subscriptions.addAll(FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(AddonUpdateObject::enabledProperty).toArray(BooleanProperty[]::new))); + }); TableView table = new TableView<>(objects); table.setEditable(true); @@ -114,6 +127,9 @@ public AddonUpdatesPage(LocalAddonManager localAddonManager, List exportList()); @@ -125,7 +141,7 @@ public AddonUpdatesPage(LocalAddonManager localAddonManager, List void setupCellValueFactory(TableColumn column, private void updateFiles() { AddonUpdateTask task = new AddonUpdateTask( - localAddonManager.getDirectory(), + localAddonDirectory, objects.stream() .filter(AddonUpdateObject::isEnabled) .map(AddonUpdateObject::getData) @@ -195,7 +211,7 @@ public ReadOnlyObjectWrapper stateProperty() { } private static final class AddonUpdateObject { - final LocalAddonFile.AddonUpdate data; + final AddonUpdate data; final BooleanProperty enabled = new SimpleBooleanProperty(); final StringProperty fileName = new SimpleStringProperty(); final StringProperty currentVersion = new SimpleStringProperty(); @@ -203,7 +219,7 @@ private static final class AddonUpdateObject { final StringProperty targetVersionType = new SimpleStringProperty(); final StringProperty source = new SimpleStringProperty(); - public AddonUpdateObject(LocalAddonFile.AddonUpdate data) { + public AddonUpdateObject(AddonUpdate data) { this.data = data; enabled.set(!data.localAddonFile().isDisabled()); @@ -217,7 +233,7 @@ public AddonUpdateObject(LocalAddonFile.AddonUpdate data) { }); } - public LocalAddonFile.AddonUpdate getData() { + public AddonUpdate getData() { return data; } @@ -255,12 +271,12 @@ public static class AddonUpdateTask extends Task { private final Collection> dependents; private final List failedAddons = new ArrayList<>(); - AddonUpdateTask(Path addonDirectory, List addons) { + AddonUpdateTask(Path addonDirectory, List addons) { setStage("addon.check_update.confirm"); getProperties().put("total", addons.size()); this.dependents = new ArrayList<>(); - for (LocalAddonFile.AddonUpdate addon : addons) { + for (AddonUpdate addon : addons) { LocalAddonFile local = addon.localAddonFile(); RemoteAddon.Version remote = addon.targetVersion(); boolean isDisabled = local.isDisabled(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java index 5ea18607dee..c2d26f73ffa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ModListPage.java @@ -259,10 +259,10 @@ public void checkUpdates(Collection mods) { if (exception != null || result == null) { Controllers.dialog(i18n("addon.check_update.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR); if (exception != null) LOG.warning("Failed to check for updates", exception); - } else if (result.isEmpty()) { + } else if (result.commonUpdates().isEmpty()) { Controllers.dialog(i18n("addon.check_update.empty")); } else { - Controllers.navigateForward(new AddonUpdatesPage<>(modManager, result)); + Controllers.navigateForward(new AddonUpdatesPage(modManager.getDirectory(), result)); } }) .withStagesHints("update.checking"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java index 1028d13ef3e..eb7ec2cb9f0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/ResourcePackListPage.java @@ -240,10 +240,10 @@ public void checkUpdates(Collection resourcePacks) { .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception != null || result == null) { Controllers.dialog(i18n("addon.check_update.failed_check"), i18n("message.failed"), MessageDialogPane.MessageType.ERROR); - } else if (result.isEmpty()) { + } else if (result.commonUpdates().isEmpty()) { Controllers.dialog(i18n("addon.check_update.empty")); } else { - Controllers.navigateForward(new AddonUpdatesPage<>(resourcePackManager, result)); + Controllers.navigateForward(new AddonUpdatesPage(resourcePackManager.getDirectory(), result)); } }) .withStagesHints("update.checking"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java index 2957f16e7f9..072f9b232d5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/DownloadSettingsPage.java @@ -103,8 +103,8 @@ public DownloadSettingsPage() { defaultAddonSourcePane.valueProperty().bindBidirectional(settings().defaultAddonSourceProperty()); var updateAddonsToPreviewButton = new LineToggleButton(); - updateAddonsToPreviewButton.setTitle(i18n("settings.launcher.update_addons_to_preview")); - updateAddonsToPreviewButton.selectedProperty().bindBidirectional(settings().updateAddonsToPreviewProperty()); + updateAddonsToPreviewButton.setTitle(i18n("settings.launcher.default_update_addons_to_preview")); + updateAddonsToPreviewButton.selectedProperty().bindBidirectional(settings().defaultUpdateAddonsToPreviewProperty()); gameContent.getContent().setAll(versionListSourcePane, downloadSourcePane, defaultAddonSourcePane, updateAddonsToPreviewButton); } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 62e6b9f3c14..31cfa15c6e3 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -186,6 +186,7 @@ addon.check_update.empty=All files are up-to-date addon.check_update.failed_check=Failed to check for updates. addon.check_update.failed_download=Failed to download some files. addon.check_update.file=File +addon.check_update.preview=Show preview versions addon.check_update.source=Source addon.check_update.target_version=Target Version addon.curseforge=CurseForge @@ -1671,7 +1672,7 @@ settings.launcher.theme_color_type.default=Default settings.launcher.theme_color_type.system=Follow System settings.launcher.title_transparent=Transparent Titlebar settings.launcher.turn_off_animations=Disable Animation -settings.launcher.update_addons_to_preview=Update Addons to Alpha/Beta Versions +settings.launcher.default_update_addons_to_preview=Update Addons to Alpha/Beta Versions by Default settings.launcher.version_list_source=Version List settings.launcher.window_transparent=Transparent Window settings.launcher.background.settings.opacity=Opacity diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 71ddffbf94b..694a5ce778f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -184,6 +184,7 @@ addon.check_update.empty=沒有需要更新的檔案 addon.check_update.failed_check=檢查更新失敗 addon.check_update.failed_download=部分檔案下載失敗 addon.check_update.file=檔案 +addon.check_update.preview=顯示 Alpha/Beta 版本 addon.check_update.source=來源 addon.check_update.target_version=目標版本 addon.curseforge=CurseForge @@ -1472,7 +1473,7 @@ settings.launcher.theme_color_type.default=預設 settings.launcher.theme_color_type.system=跟隨系統 settings.launcher.title_transparent=標題欄透明 settings.launcher.turn_off_animations=關閉動畫 -settings.launcher.update_addons_to_preview=更新附加內容到 Alpha/Beta 版本 +settings.launcher.default_update_addons_to_preview=預設更新附加內容到 Alpha/Beta 版本 settings.launcher.version_list_source=版本清單來源 settings.launcher.window_transparent=視窗透明 settings.launcher.background.settings.opacity=不透明度 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 08ff8e51bdc..8ee398ab47c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -186,6 +186,7 @@ addon.check_update.empty=没有需要更新的文件 addon.check_update.failed_check=检查更新失败 addon.check_update.failed_download=部分文件下载失败 addon.check_update.file=文件 +addon.check_update.preview=显示测试版本 addon.check_update.source=来源 addon.check_update.target_version=目标版本 addon.curseforge=CurseForge @@ -1472,7 +1473,7 @@ settings.launcher.theme_color_type.default=默认 settings.launcher.theme_color_type.system=跟随系统 settings.launcher.title_transparent=标题栏透明 settings.launcher.turn_off_animations=关闭动画 -settings.launcher.update_addons_to_preview=更新附加内容到测试版本 +settings.launcher.default_update_addons_to_preview=默认更新附加内容到测试版本 settings.launcher.version_list_source=版本列表源 settings.launcher.window_transparent=窗口透明 settings.launcher.background.settings.opacity=不透明度 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonUpdate.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonUpdate.java new file mode 100644 index 00000000000..66de27bd8bf --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/AddonUpdate.java @@ -0,0 +1,37 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.addon; + +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.Predicate; + +public record AddonUpdate( + LocalAddonFile localAddonFile, + RemoteAddon.Version currentVersion, + RemoteAddon.Version targetVersion +) { + + @SuppressWarnings("RedundantRecordConstructor") + public record UpdateConditions(RemoteAddon.Type type, @Nullable List> predicates) { + public UpdateConditions { + } + } + +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 90443a38518..28307d6fbf9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.addon; import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jetbrains.annotations.Nullable; @@ -25,7 +26,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.*; -import java.util.function.Predicate; import java.util.stream.Collectors; /// Sub-classes should implement `Comparable` @@ -52,12 +52,13 @@ public boolean isDisabled() { public abstract void delete() throws IOException; @Nullable - protected UpdateConditions getUpdateConditions() { + protected AddonUpdate.UpdateConditions getUpdateConditions() { return null; } + /// @return A pair of addon update info, the first for all versions and the second for release versions only (might be null). @Nullable - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source, boolean updateToPreview) throws IOException { + public Pair checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { var conditions = getUpdateConditions(); if (conditions == null) return null; @@ -65,36 +66,31 @@ public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVe if (repository == null) return null; Optional currentVersion = repository.getRemoteVersionByLocalFile(getFile()); if (currentVersion.isEmpty()) return null; + var current = currentVersion.orElseThrow(); var stream = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().projectId()) .filter(version -> version.gameVersions().contains(gameVersion)); - if (!updateToPreview) - stream = stream.filter(version -> version.versionType() == RemoteAddon.VersionType.Release); + if (current.gameVersions().contains(gameVersion)) // Otherwise it means we are upgrading from another game version + stream = stream.filter(version -> version.datePublished().isAfter(current.datePublished())); if (conditions.predicates() != null) - for (var p : conditions.predicates()) { + for (var p : conditions.predicates()) stream = stream.filter(p); - } + List remoteVersions = stream.sorted(Comparator.comparing(RemoteAddon.Version::datePublished).reversed()).toList(); if (remoteVersions.isEmpty()) return null; - return new AddonUpdate(this, currentVersion.get(), remoteVersions.get(0)); + var release = remoteVersions.stream() + .filter(v -> v.versionType() == RemoteAddon.VersionType.Release) + .findFirst().orElse(null); + + return Pair.pair( + new AddonUpdate(this, current, remoteVersions.get(0)), // All channels + release != null ? new AddonUpdate(this, current, release) : null // Release channel + ); } public void onUpdated(String newFileNameWithExt) { } - @SuppressWarnings("RedundantRecordConstructor") - protected record UpdateConditions(RemoteAddon.Type type, @Nullable List> predicates) { - public UpdateConditions { - } - } - - public record AddonUpdate( - LocalAddonFile localAddonFile, - RemoteAddon.Version currentVersion, - RemoteAddon.Version targetVersion - ) { - } - public static class Description { private final List parts; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index 97994df2887..2d666b9364b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -19,6 +19,7 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; +import org.jackhuang.hmcl.addon.AddonUpdate; import org.jackhuang.hmcl.addon.LocalAddonFile; import org.jackhuang.hmcl.addon.LocalAddonManager; import org.jackhuang.hmcl.addon.RemoteAddon; @@ -193,8 +194,8 @@ public void delete() throws IOException { } @Override - protected UpdateConditions getUpdateConditions() { - return new UpdateConditions(RemoteAddon.Type.MOD, List.of(v -> v.loaders().contains(getModLoaderType()))); + protected AddonUpdate.UpdateConditions getUpdateConditions() { + return new AddonUpdate.UpdateConditions(RemoteAddon.Type.MOD, List.of(v -> v.loaders().contains(getModLoaderType()))); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java index 3cde605daa7..d75af09ca86 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackZipFile.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.addon.resourcepack; import javafx.scene.image.Image; +import org.jackhuang.hmcl.addon.AddonUpdate; import org.jackhuang.hmcl.addon.RemoteAddon; import org.jackhuang.hmcl.addon.meta.PackMcMeta; import org.jackhuang.hmcl.util.io.CompressingUtils; @@ -32,7 +33,7 @@ final class ResourcePackZipFile extends ResourcePackFile { - private static final UpdateConditions UPDATE_CONDITIONS = new UpdateConditions(RemoteAddon.Type.RESOURCE_PACK, null); + private static final AddonUpdate.UpdateConditions UPDATE_CONDITIONS = new AddonUpdate.UpdateConditions(RemoteAddon.Type.RESOURCE_PACK, null); private final PackMcMeta meta; private final @Nullable Image icon; @@ -88,7 +89,7 @@ public void delete() throws IOException { } @Override - protected UpdateConditions getUpdateConditions() { + protected AddonUpdate.UpdateConditions getUpdateConditions() { return UPDATE_CONDITIONS; } From c7d674095a2806f36440a9fff48935d75294623f Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 6 Aug 2026 11:30:53 +0800 Subject: [PATCH 15/17] update --- .../hmcl/ui/instances/AddonUpdatesPage.java | 16 ++++++---------- .../org/jackhuang/hmcl/addon/RemoteAddon.java | 2 ++ .../CurseForgeRemoteAddonRepository.java | 2 +- .../addon/resourcepack/ResourcePackManager.java | 2 ++ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java index 7a817b43e38..82548d0404f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java @@ -329,13 +329,10 @@ public AddonChangelog(AddonUpdateObject object) { private void loadChangelog(AddonUpdateObject object, SpinnerPane spinnerPane, ScrollPane scrollPane) { spinnerPane.setLoading(true); - RemoteAddonRepository repo = object.data.source().getRepoForType(object.data.repoType()); + RemoteAddonRepository repo = object.data.targetVersion().source().getCommonRepo(); Task.supplyAsync(() -> { - if (object.changelog != null) { - return object.changelog; - } + if (object.changelog != null) return object.changelog; RemoteAddon.Version version = object.data.targetVersion(); - if (repo == null) return null; return StringUtils.convertToHtml( repo.getAddonChangelog(DownloadProviders.getDownloadProvider(), version.projectId(), version.versionId()), "238222".equals(object.data.targetVersion().projectId()) @@ -343,7 +340,7 @@ private void loadChangelog(AddonUpdateObject object, SpinnerPane spinnerPane, Sc }).whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception == null) { object.changelog = StringUtils.isNotBlank(result) ? result : i18n("addon.changelog.empty"); - scrollPane.setContent(FXUtils.renderAddonChangelog(object.changelog, repo == null ? "" : repo.getBaseUrl())); + scrollPane.setContent(FXUtils.renderAddonChangelog(object.changelog, repo.getBaseUrl())); FXUtils.smoothScrolling(scrollPane); spinnerPane.setFailedReason(null); } else { @@ -354,10 +351,9 @@ private void loadChangelog(AddonUpdateObject object, SpinnerPane spinnerPane, Sc } private void loadVersionPageUrl(AddonUpdateObject object, JFXHyperlink button) { - Task.supplyAsync(() -> { - RemoteAddonRepository repo = object.data.source().getRepoForType(object.data.repoType()); - return repo == null ? null : repo.getVersionPageUrl(object.data.targetVersion()); - }).whenComplete(Schedulers.javafx(), (result, exception) -> { + Task.supplyAsync(() -> + object.data.targetVersion().source().getCommonRepo().getVersionPageUrl(object.data.targetVersion()) + ).whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception == null && StringUtils.isNotBlank(result)) { button.setOnAction(__ -> Controllers.openUriInBrowser(result)); button.setDisable(false); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java index e3059e0ac67..980c3156521 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java @@ -22,6 +22,7 @@ import org.jackhuang.hmcl.addon.repository.ModrinthRemoteAddonRepository; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -180,6 +181,7 @@ public RemoteAddonRepository getRepoForType(Type type) { }; } + @NotNull public RemoteAddonRepository getCommonRepo() { return commonRepo; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java index 637f85ed404..71fb4324ba0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/repository/CurseForgeRemoteAddonRepository.java @@ -539,7 +539,7 @@ public RemoteAddon.Version toVersion() { return new RemoteAddon.Version( RemoteAddon.Source.CURSEFORGE, Integer.toString(id()), - Integer.toString(modId), + Integer.toString(modId()), displayName(), fileName(), fileDate(), diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java index 45f7fa9659e..9a73f5f68cc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java @@ -508,6 +508,8 @@ public boolean isEnabled(ResourcePackFile resourcePack) { } public boolean rename(String oldName, String newName) { + if (newName.equals(oldName)) return false; + String oldNameNewFormat = "file/" + oldName; String newNameNewFormat = "file/" + newName; boolean modified = false; From 8100fd04c9c265896dd9b7ce3426414d6dd52907 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 6 Aug 2026 11:34:04 +0800 Subject: [PATCH 16/17] update --- .../src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java | 1 - 1 file changed, 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java index 32f3c36e541..28307d6fbf9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/LocalAddonFile.java @@ -21,7 +21,6 @@ import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jetbrains.annotations.NotNullByDefault; import org.jetbrains.annotations.Nullable; import java.io.IOException; From c2947ec7c9bc3232699d6050cc2d7f82f5bf9c65 Mon Sep 17 00:00:00 2001 From: ToobLac Date: Thu, 6 Aug 2026 11:44:31 +0800 Subject: [PATCH 17/17] update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index 526c2c0eb83..2d5ea1c9327 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -1121,6 +1121,7 @@ public AllListener(int childSelectedCount) { } } } + return new AllListener(childSelectedCount).subscriptions; }