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 531bbaa94fb..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; @@ -30,19 +31,17 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class AddonCheckUpdatesTask extends Task> { - private final DownloadProvider downloadProvider; +public class AddonCheckUpdatesTask extends Task> { private final List> dependents; - public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { - this.downloadProvider = downloadProvider; + public AddonCheckUpdatesTask(DownloadProvider downloadProvider, String gameVersion, Collection addons) { dependents = addons.stream().map(addon -> Task.supplyAsync(Schedulers.io(), () -> { LocalAddonFile.AddonUpdate candidate = null; 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); } @@ -55,6 +54,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/instances/AddonUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/AddonUpdatesPage.java index fdfdf65ff45..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().self().getSource()) { - 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 { @@ -293,35 +264,30 @@ 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 newFileName = 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) - ); - - 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); 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(newFileName); + if (!local.keepOldFiles()) { + try { + local.delete(); + } catch (IOException e) { + LOG.warning("Failed to delete outdated addon: " + local.getFile(), e); + } } } }) 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 cc11d1d7125..5ea18607dee 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 @@ -247,12 +247,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 { 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 f98fa0fc46e..1028d13ef3e 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 @@ -235,7 +235,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) { 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 a7a9936b057..2957f16e7f9 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 @@ -65,8 +65,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"); @@ -102,10 +102,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 0435f5e429d..62e6b9f3c14 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 @@ -1623,6 +1624,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 @@ -1669,6 +1671,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.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 44b8c8924ee..71ddffbf94b 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=發布日期 @@ -1424,6 +1425,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=匯出啟動器日誌 @@ -1470,6 +1472,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.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 d4555889a8c..08ff8e51bdc 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=发布日期 @@ -1424,6 +1425,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=导出启动器日志 @@ -1470,6 +1472,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 ab6c9d4aeca..90443a38518 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` @@ -53,13 +52,46 @@ public boolean isDisabled() { public abstract void delete() throws IOException; @Nullable - public abstract AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException; + protected UpdateConditions getUpdateConditions() { + return null; + } + + @Nullable + public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source, boolean updateToPreview) throws IOException { + var conditions = getUpdateConditions(); + if (conditions == null) return null; + + RemoteAddonRepository repository = source.getRepoForType(conditions.type()); + if (repository == null) return null; + Optional currentVersion = repository.getRemoteVersionByLocalFile(getFile()); + if (currentVersion.isEmpty()) return null; + + 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 (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)); + } + + 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, - boolean useRemoteFileName + RemoteAddon.Version targetVersion ) { } 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 8873b3ee367..e9a539a0976 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/RemoteAddon.java @@ -218,11 +218,7 @@ public interface IAddon { Stream loadVersions(RemoteAddonRepository repo, DownloadProvider downloadProvider) throws IOException; } - public interface IVersion { - Source getSource(); - } - - public record Version(IVersion self, String projectId, String name, String version, String changelog, + public record Version(RemoteAddon.Source source, String projectId, 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 cb35a065563..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 @@ -22,8 +22,6 @@ 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; @@ -195,19 +193,8 @@ public void delete() throws IOException { } @Override - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { - RemoteAddonRepository repository = source.getRepoForType(RemoteAddon.Type.MOD); - if (repository == null) return null; - Optional currentVersion = repository.getRemoteVersionByLocalFile(file); - if (currentVersion.isEmpty()) return null; - List remoteVersions = repository.getRemoteVersionsById(downloadProvider, currentVersion.get().projectId()) - .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(RemoteAddon.Type.MOD, 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 db6c0eb3da1..4511121d735 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 @@ -465,7 +465,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() { @@ -477,11 +477,6 @@ public String downloadUrl() { return downloadUrl; } - @Override - public RemoteAddon.Source getSource() { - return RemoteAddon.Source.CURSEFORGE; - } - public RemoteAddon.Version toVersion() { RemoteAddon.VersionType versionType = switch (releaseType()) { case 1 -> RemoteAddon.VersionType.Release; @@ -491,11 +486,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 62df6c65dcb..73c945037b1 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 @@ -383,7 +383,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), @@ -391,33 +391,23 @@ public record ProjectVersion(String name, @SerializedName("version_number") Stri pair("incompatible", RemoteAddon.DependencyType.INCOMPATIBLE) ); - @Override - public RemoteAddon.Source getSource() { - return RemoteAddon.Source.MODRINTH; - } - 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(); } 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/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/ResourcePackManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/resourcepack/ResourcePackManager.java index aee0e34e31e..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 @@ -507,6 +507,37 @@ 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(); + 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(supportsNewOptionsFormat ? newNameNewFormat : 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 c032aa73e35..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,9 +18,7 @@ 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 +27,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(RemoteAddon.Type.RESOURCE_PACK, null); + private final PackMcMeta meta; private final @Nullable Image icon; @@ -90,18 +88,14 @@ public void delete() throws IOException { } @Override - public AddonUpdate checkUpdates(DownloadProvider downloadProvider, String gameVersion, RemoteAddon.Source source) throws IOException { - RemoteAddonRepository repository = source.getRepoForType(RemoteAddon.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().projectId()) - .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; + } + + @Override + public void onUpdated(String newFileNameWithExt) { + super.onUpdated(newFileNameWithExt); + manager.rename(getFileNameWithExtension(), newFileNameWithExt); } }