From 9baff1f82395a317038b2340b4120a5a032301f3 Mon Sep 17 00:00:00 2001 From: Max Henkel Date: Tue, 5 Dec 2023 12:41:47 +0100 Subject: [PATCH 1/5] Add support for custom update checking logic --- .../com/terraformersmc/modmenu/ModMenu.java | 7 +- .../modmenu/api/ModMenuApi.java | 10 ++ .../modmenu/api/UpdateChecker.java | 7 + .../modmenu/api/UpdateInfo.java | 29 ++++ .../gui/widget/DescriptionListWidget.java | 40 ++++- .../gui/widget/entries/ModListEntry.java | 2 +- .../modmenu/util/ModrinthUtil.java | 157 ++++++++++-------- .../terraformersmc/modmenu/util/mod/Mod.java | 27 ++- .../modmenu/util/mod/ModSearch.java | 3 +- .../modmenu/util/mod/ModrinthData.java | 8 - .../modmenu/util/mod/ModrinthUpdateInfo.java | 39 +++++ .../util/mod/fabric/FabricDummyParentMod.java | 23 ++- .../modmenu/util/mod/fabric/FabricMod.java | 30 +++- .../resources/assets/modmenu/lang/en_us.json | 2 + 14 files changed, 272 insertions(+), 112 deletions(-) create mode 100644 src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java create mode 100644 src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java delete mode 100644 src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java create mode 100644 src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenu.java b/src/main/java/com/terraformersmc/modmenu/ModMenu.java index 5ce9abd28..5c39ad027 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenu.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenu.java @@ -6,6 +6,7 @@ import com.google.gson.GsonBuilder; import com.terraformersmc.modmenu.api.ConfigScreenFactory; import com.terraformersmc.modmenu.api.ModMenuApi; +import com.terraformersmc.modmenu.api.UpdateChecker; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.config.ModMenuConfigManager; import com.terraformersmc.modmenu.event.ModMenuEventHandler; @@ -68,6 +69,7 @@ public static Screen getConfigScreen(String modid, Screen menuScreen) { public void onInitializeClient() { ModMenuConfigManager.initializeConfig(); Set modpackMods = new HashSet<>(); + Map updateCheckers = new HashMap<>(); FabricLoader.getInstance().getEntrypointContainers("modmenu", ModMenuApi.class).forEach(entrypoint -> { ModMetadata metadata = entrypoint.getProvider().getMetadata(); String modId = metadata.getId(); @@ -75,6 +77,7 @@ public void onInitializeClient() { ModMenuApi api = entrypoint.getEntrypoint(); configScreenFactories.put(modId, api.getModConfigScreenFactory()); apiImplementations.add(api); + updateCheckers.put(modId, api.getUpdateChecker()); api.attachModpackBadges(modpackMods::add); } catch (Throwable e) { LOGGER.error("Mod {} provides a broken implementation of ModMenuApi", modId, e); @@ -91,6 +94,8 @@ public void onInitializeClient() { mod = new FabricMod(modContainer, modpackMods); } + mod.setUpdateChecker(updateCheckers.get(mod.getId())); + MODS.put(mod.getId(), mod); } @@ -136,7 +141,7 @@ public static boolean areModUpdatesAvailable() { continue; } - if (mod.getModrinthData() != null || mod.getChildHasUpdate()) { + if (mod.hasUpdate() || mod.getChildHasUpdate()) { return true; // At least one currently visible mod has an update } } diff --git a/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java b/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java index ef66bdc92..4a6bc27b9 100644 --- a/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java +++ b/src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java @@ -43,6 +43,16 @@ default ConfigScreenFactory getModConfigScreenFactory() { return screen -> null; } + /** + * Used for mods that have their own update checking logic. + * By returning your own {@link UpdateChecker} instance, you can override ModMenus built-in update checking logic. + * + * @return An {@link UpdateChecker} or null if ModMenu should handle update checking. + */ + default UpdateChecker getUpdateChecker() { + return null; + } + /** * Used to provide config screen factories for other mods. This takes second * priority to a mod's own config screen factory provider. For example, if diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java new file mode 100644 index 000000000..87ce1886c --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java @@ -0,0 +1,7 @@ +package com.terraformersmc.modmenu.api; + +public interface UpdateChecker { + + UpdateInfo checkForUpdates(); + +} diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java new file mode 100644 index 000000000..98fc89160 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java @@ -0,0 +1,29 @@ +package com.terraformersmc.modmenu.api; + +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +public interface UpdateInfo { + + /** + * @return If an update for the mod is available. + */ + boolean isUpdateAvailable(); + + /** + * @return The message that is getting displayed when an update is available or null to let ModMenu handle displaying the message. + */ + @Nullable + default Text getUpdateMessage() { + return null; + } + + /** + * @return The URL to the mod download or null if there is no download link available. + */ + @Nullable + default String getDownloadLink() { + return null; + } + +} diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index 836f64940..06fed8a3d 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -2,11 +2,13 @@ import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.gui.ModsScreen; import com.terraformersmc.modmenu.gui.widget.entries.ModListEntry; import com.terraformersmc.modmenu.util.VersionUtil; import com.terraformersmc.modmenu.util.mod.Mod; +import com.terraformersmc.modmenu.util.mod.ModrinthUpdateInfo; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -37,6 +39,8 @@ public class DescriptionListWidget extends EntryListWidget { LOGGER.info("Checking mod updates..."); + checkForModrinthUpdates(); + checkForCustomUpdates(); + }); + } - Map> modHashes = new HashMap<>(); - new ArrayList<>(ModMenu.MODS.values()).stream().filter(ModrinthUtil::allowsUpdateChecks).forEach(mod -> { - String modId = mod.getId(); + public static void checkForCustomUpdates() { + ModMenu.MODS.values().stream().filter(ModrinthUtil::allowsUpdateChecks).forEach(mod -> { + UpdateChecker updateChecker = mod.getUpdateChecker(); + if (updateChecker == null) { + return; + } + mod.setUpdateInfo(updateChecker.checkForUpdates()); + }); + } - try { - String hash = mod.getSha512Hash(); + public static void checkForModrinthUpdates() { + if (apiV2Deprecated) { + return; + } - if (hash != null) { - LOGGER.debug("Hash for {} is {}", modId, hash); - modHashes.putIfAbsent(hash, new HashSet<>()); - modHashes.get(hash).add(mod); - } - } catch (IOException e) { - LOGGER.error("Error getting mod hash for mod {}: ", modId, e); - } - }); - - String environment = ModMenu.devEnvironment ? "/development": ""; - String primaryLoader = ModMenu.runningQuilt ? "quilt" : "fabric"; - List loaders = ModMenu.runningQuilt ? List.of("fabric", "quilt") : List.of("fabric"); - - String mcVer = SharedConstants.getGameVersion().getName(); - String[] splitVersion = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID) - .get().getMetadata().getVersion().getFriendlyString().split("\\+", 1); // Strip build metadata for privacy - final var modMenuVersion = splitVersion.length > 1 ? splitVersion[1] : splitVersion[0]; - final var userAgent = "%s/%s (%s/%s%s)".formatted(ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment); - String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer)); - LOGGER.debug("User agent: " + userAgent); - LOGGER.debug("Body: " + body); - var latestVersionsRequest = HttpRequest.newBuilder() - .POST(HttpRequest.BodyPublishers.ofString(body)) - .header("User-Agent", userAgent) - .header("Content-Type", "application/json") - .uri(URI.create("https://api.modrinth.com/v2/version_files/update")) - .build(); + Map> modHashes = new HashMap<>(); + new ArrayList<>(ModMenu.MODS.values()).stream().filter(ModrinthUtil::allowsUpdateChecks).filter(mod -> mod.getUpdateChecker() == null).forEach(mod -> { + String modId = mod.getId(); try { - var latestVersionsResponse = client.send(latestVersionsRequest, HttpResponse.BodyHandlers.ofString()); - - int status = latestVersionsResponse.statusCode(); - LOGGER.debug("Status: " + status); - if (status == 410) { - apiV2Deprecated = true; - LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates."); - } else if (status == 200) { - JsonObject responseObject = JsonParser.parseString(latestVersionsResponse.body()).getAsJsonObject(); - LOGGER.debug(String.valueOf(responseObject)); - responseObject.asMap().forEach((lookupHash, versionJson) -> { - var versionObj = versionJson.getAsJsonObject(); - var projectId = versionObj.get("project_id").getAsString(); - var versionNumber = versionObj.get("version_number").getAsString(); - var versionId = versionObj.get("id").getAsString(); - var primaryFile = versionObj.get("files").getAsJsonArray().asList().stream() - .filter(file -> file.getAsJsonObject().get("primary").getAsBoolean()).findFirst(); - - if (primaryFile.isEmpty()) { - return; - } - - var versionHash = primaryFile.get().getAsJsonObject().get("hashes").getAsJsonObject().get("sha512").getAsString(); - - if (!Objects.equals(versionHash, lookupHash)) { - // hashes different, there's an update. - modHashes.get(lookupHash).forEach(mod -> { - LOGGER.info("Update available for '{}@{}', (-> {})", mod.getId(), mod.getVersion(), versionNumber); - mod.setModrinthData(new ModrinthData(projectId, versionId, versionNumber)); - }); - } - }); + String hash = mod.getSha512Hash(); + + if (hash != null) { + LOGGER.debug("Hash for {} is {}", modId, hash); + modHashes.putIfAbsent(hash, new HashSet<>()); + modHashes.get(hash).add(mod); } - } catch (IOException | InterruptedException e) { - LOGGER.error("Error checking for updates: ", e); + } catch (IOException e) { + LOGGER.error("Error getting mod hash for mod {}: ", modId, e); } }); + + String environment = ModMenu.devEnvironment ? "/development" : ""; + String primaryLoader = ModMenu.runningQuilt ? "quilt" : "fabric"; + List loaders = ModMenu.runningQuilt ? List.of("fabric", "quilt") : List.of("fabric"); + + String mcVer = SharedConstants.getGameVersion().getName(); + String[] splitVersion = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID) + .get().getMetadata().getVersion().getFriendlyString().split("\\+", 1); // Strip build metadata for privacy + final var modMenuVersion = splitVersion.length > 1 ? splitVersion[1] : splitVersion[0]; + final var userAgent = "%s/%s (%s/%s%s)".formatted(ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment); + String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer)); + LOGGER.debug("User agent: " + userAgent); + LOGGER.debug("Body: " + body); + var latestVersionsRequest = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(body)) + .header("User-Agent", userAgent) + .header("Content-Type", "application/json") + .uri(URI.create("https://api.modrinth.com/v2/version_files/update")) + .build(); + + try { + var latestVersionsResponse = client.send(latestVersionsRequest, HttpResponse.BodyHandlers.ofString()); + + int status = latestVersionsResponse.statusCode(); + LOGGER.debug("Status: " + status); + if (status == 410) { + apiV2Deprecated = true; + LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates."); + } else if (status == 200) { + JsonObject responseObject = JsonParser.parseString(latestVersionsResponse.body()).getAsJsonObject(); + LOGGER.debug(String.valueOf(responseObject)); + responseObject.asMap().forEach((lookupHash, versionJson) -> { + var versionObj = versionJson.getAsJsonObject(); + var projectId = versionObj.get("project_id").getAsString(); + var versionNumber = versionObj.get("version_number").getAsString(); + var versionId = versionObj.get("id").getAsString(); + var primaryFile = versionObj.get("files").getAsJsonArray().asList().stream() + .filter(file -> file.getAsJsonObject().get("primary").getAsBoolean()).findFirst(); + + if (primaryFile.isEmpty()) { + return; + } + + var versionHash = primaryFile.get().getAsJsonObject().get("hashes").getAsJsonObject().get("sha512").getAsString(); + + if (!Objects.equals(versionHash, lookupHash)) { + // hashes different, there's an update. + modHashes.get(lookupHash).forEach(mod -> { + LOGGER.info("Update available for '{}@{}', (-> {})", mod.getId(), mod.getVersion(), versionNumber); + mod.setUpdateInfo(new ModrinthUpdateInfo(projectId, versionId, versionNumber)); + }); + } + }); + } + } catch (IOException | InterruptedException e) { + LOGGER.error("Error checking for updates: ", e); + } } public static void triggerV2DeprecatedToast() { diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java index 44a371ab7..22c95d6a3 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/Mod.java @@ -1,16 +1,14 @@ package com.terraformersmc.modmenu.util.mod; -import com.google.common.hash.Hashing; -import com.google.common.io.Files; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.mod.fabric.FabricIconHandler; -import net.fabricmc.loader.api.metadata.ModOrigin; import net.minecraft.client.resource.language.I18n; import net.minecraft.client.texture.NativeImageBackedTexture; import net.minecraft.text.Text; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.quiltmc.loader.api.QuiltLoader; import java.io.IOException; import java.util.*; @@ -99,17 +97,30 @@ default String getTranslatedDescription() { boolean isReal(); + boolean allowsUpdateChecks(); + @Nullable - ModrinthData getModrinthData(); + UpdateChecker getUpdateChecker(); - boolean allowsUpdateChecks(); + void setUpdateChecker(@Nullable UpdateChecker updateChecker); + + @Nullable + UpdateInfo getUpdateInfo(); + + void setUpdateInfo(@Nullable UpdateInfo updateInfo); + + default boolean hasUpdate() { + UpdateInfo updateInfo = getUpdateInfo(); + if (updateInfo == null) { + return false; + } + return updateInfo.isUpdateAvailable(); + } default @Nullable String getSha512Hash() throws IOException { return null; } - void setModrinthData(ModrinthData modrinthData); - void setChildHasUpdate(); boolean getChildHasUpdate(); diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java index 1155cf834..437770f68 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java @@ -6,7 +6,6 @@ import net.minecraft.client.resource.language.I18n; import net.minecraft.util.Pair; -import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; @@ -68,7 +67,7 @@ private static int passesFilters(ModsScreen screen, Mod mod, String query) { || deprecated.contains(query) && mod.getBadges().contains(Mod.Badge.DEPRECATED) // Search for deprecated mods || clientside.contains(query) && mod.getBadges().contains(Mod.Badge.CLIENT) // Search for clientside mods || configurable.contains(query) && screen.getModHasConfigScreen().get(modId) // Search for mods that can be configured - || hasUpdate.contains(query) && mod.getModrinthData() != null // Search for mods that have updates + || hasUpdate.contains(query) && hasUpdate() // Search for mods that have updates ) { return 1; } diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java deleted file mode 100644 index b343611ef..000000000 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthData.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.terraformersmc.modmenu.util.mod; - -public record ModrinthData( - String projectId, - String versionId, - String versionNumber -) { -} diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java new file mode 100644 index 000000000..9a17b5a46 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/ModrinthUpdateInfo.java @@ -0,0 +1,39 @@ +package com.terraformersmc.modmenu.util.mod; + +import com.terraformersmc.modmenu.api.UpdateInfo; + +public class ModrinthUpdateInfo implements UpdateInfo { + + protected String projectId; + protected String versionId; + protected String versionNumber; + + public ModrinthUpdateInfo(String projectId, String versionId, String versionNumber) { + this.projectId = projectId; + this.versionId = versionId; + this.versionNumber = versionNumber; + } + + @Override + public boolean isUpdateAvailable() { + return true; + } + + @Override + public String getDownloadLink() { + return "https://modrinth.com/project/%s/version/%s".formatted(projectId, versionId); + } + + public String getProjectId() { + return projectId; + } + + public String getVersionId() { + return versionId; + } + + public String getVersionNumber() { + return versionNumber; + } + +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java index 4d775eef9..d7d32d732 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricDummyParentMod.java @@ -1,9 +1,10 @@ package com.terraformersmc.modmenu.util.mod.fabric; import com.terraformersmc.modmenu.ModMenu; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.mod.Mod; -import com.terraformersmc.modmenu.util.mod.ModrinthData; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.client.texture.NativeImageBackedTexture; @@ -157,18 +158,28 @@ public boolean isReal() { } @Override - public @Nullable ModrinthData getModrinthData() { + public boolean allowsUpdateChecks() { + return false; + } + + @Override + public @Nullable UpdateChecker getUpdateChecker() { return null; } @Override - public void setModrinthData(ModrinthData modrinthData) { - // Not a real mod, won't exist on Modrinth + public void setUpdateChecker(@Nullable UpdateChecker updateChecker) { + } @Override - public boolean allowsUpdateChecks() { - return false; + public @Nullable UpdateInfo getUpdateInfo() { + return null; + } + + @Override + public void setUpdateInfo(@Nullable UpdateInfo updateInfo) { + } @Override diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java index b9d0dc941..506fbbc43 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/fabric/FabricMod.java @@ -5,11 +5,12 @@ import com.google.common.hash.Hashing; import com.google.common.io.Files; import com.terraformersmc.modmenu.ModMenu; +import com.terraformersmc.modmenu.api.UpdateChecker; +import com.terraformersmc.modmenu.api.UpdateInfo; import com.terraformersmc.modmenu.config.ModMenuConfig; import com.terraformersmc.modmenu.util.OptionalUtil; import com.terraformersmc.modmenu.util.VersionUtil; import com.terraformersmc.modmenu.util.mod.Mod; -import com.terraformersmc.modmenu.util.mod.ModrinthData; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.metadata.*; @@ -37,7 +38,8 @@ public class FabricMod implements Mod { protected final Map links = new HashMap<>(); - protected @Nullable ModrinthData modrinthData = null; + protected @Nullable UpdateChecker updateChecker = null; + protected @Nullable UpdateInfo updateInfo = null; protected boolean defaultIconWarning = true; @@ -277,20 +279,30 @@ public boolean isReal() { } @Override - public @Nullable ModrinthData getModrinthData() { - return this.modrinthData; + public boolean allowsUpdateChecks() { + return this.allowsUpdateChecks || ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(this.getId()); } @Override - public boolean allowsUpdateChecks() { - return this.allowsUpdateChecks || ModMenuConfig.DISABLE_UPDATE_CHECKER.getValue().contains(this.getId()); + public @Nullable UpdateChecker getUpdateChecker() { + return updateChecker; + } + + @Override + public void setUpdateChecker(@Nullable UpdateChecker updateChecker) { + this.updateChecker = updateChecker; + } + + @Override + public @Nullable UpdateInfo getUpdateInfo() { + return updateInfo; } @Override - public void setModrinthData(ModrinthData modrinthData) { - this.modrinthData = modrinthData; + public void setUpdateInfo(@Nullable UpdateInfo updateInfo) { + this.updateInfo = updateInfo; String parent = getParent(); - if (parent != null && modrinthData != null) { + if (parent != null && updateInfo != null && updateInfo.isUpdateAvailable()) { ModMenu.MODS.get(parent).setChildHasUpdate(); } } diff --git a/src/main/resources/assets/modmenu/lang/en_us.json b/src/main/resources/assets/modmenu/lang/en_us.json index 5d02428b6..154810d91 100644 --- a/src/main/resources/assets/modmenu/lang/en_us.json +++ b/src/main/resources/assets/modmenu/lang/en_us.json @@ -63,6 +63,8 @@ "modmenu.experimental": "(Mod Menu update checker is experimental!)", "modmenu.childHasUpdate": "A child of this mod has an update available.", "modmenu.updateText": "v%s on %s", + "modmenu.downloadLink": "Download", + "modmenu.noDownloadLink": "Download link not available", "modmenu.buymeacoffee": "Buy Me a Coffee", "modmenu.coindrop": "Coindrop", From 8bc285e6a48b6f738b0db790f2ca80b0a32fa514 Mon Sep 17 00:00:00 2001 From: Max Henkel Date: Tue, 5 Dec 2023 13:22:11 +0100 Subject: [PATCH 2/5] Rename ModrinthUtil to UpdateCheckerUtil --- .../java/com/terraformersmc/modmenu/ModMenu.java | 4 ++-- .../modmenu/event/ModMenuEventHandler.java | 4 ++-- .../{ModrinthUtil.java => UpdateCheckerUtil.java} | 14 +++++++------- .../modmenu/util/mod/quilt/QuiltMod.java | 7 +++---- 4 files changed, 14 insertions(+), 15 deletions(-) rename src/main/java/com/terraformersmc/modmenu/util/{ModrinthUtil.java => UpdateCheckerUtil.java} (92%) diff --git a/src/main/java/com/terraformersmc/modmenu/ModMenu.java b/src/main/java/com/terraformersmc/modmenu/ModMenu.java index 5c39ad027..07ddcd3ab 100644 --- a/src/main/java/com/terraformersmc/modmenu/ModMenu.java +++ b/src/main/java/com/terraformersmc/modmenu/ModMenu.java @@ -11,7 +11,7 @@ import com.terraformersmc.modmenu.config.ModMenuConfigManager; import com.terraformersmc.modmenu.event.ModMenuEventHandler; import com.terraformersmc.modmenu.util.ModMenuScreenTexts; -import com.terraformersmc.modmenu.util.ModrinthUtil; +import com.terraformersmc.modmenu.util.UpdateCheckerUtil; import com.terraformersmc.modmenu.util.mod.Mod; import com.terraformersmc.modmenu.util.mod.fabric.FabricDummyParentMod; import com.terraformersmc.modmenu.util.mod.fabric.FabricMod; @@ -99,7 +99,7 @@ public void onInitializeClient() { MODS.put(mod.getId(), mod); } - ModrinthUtil.checkForUpdates(); + UpdateCheckerUtil.checkForUpdates(); Map dummyParents = new HashMap<>(); diff --git a/src/main/java/com/terraformersmc/modmenu/event/ModMenuEventHandler.java b/src/main/java/com/terraformersmc/modmenu/event/ModMenuEventHandler.java index 1d9291e52..e27c22d2d 100644 --- a/src/main/java/com/terraformersmc/modmenu/event/ModMenuEventHandler.java +++ b/src/main/java/com/terraformersmc/modmenu/event/ModMenuEventHandler.java @@ -6,7 +6,7 @@ import com.terraformersmc.modmenu.gui.ModsScreen; import com.terraformersmc.modmenu.gui.widget.ModMenuButtonWidget; import com.terraformersmc.modmenu.gui.widget.UpdateCheckerTexturedButtonWidget; -import com.terraformersmc.modmenu.util.ModrinthUtil; +import com.terraformersmc.modmenu.util.UpdateCheckerUtil; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents; @@ -91,7 +91,7 @@ private static void afterTitleScreenInit(Screen screen) { } } } - ModrinthUtil.triggerV2DeprecatedToast(); + UpdateCheckerUtil.triggerV2DeprecatedToast(); } private static void onClientEndTick(MinecraftClient client) { diff --git a/src/main/java/com/terraformersmc/modmenu/util/ModrinthUtil.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java similarity index 92% rename from src/main/java/com/terraformersmc/modmenu/util/ModrinthUtil.java rename to src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java index 0cb35de6f..6870574e3 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/ModrinthUtil.java +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java @@ -24,11 +24,11 @@ import java.net.http.HttpResponse; import java.util.*; -public class ModrinthUtil { +public class UpdateCheckerUtil { public static final Logger LOGGER = LoggerFactory.getLogger("Mod Menu/Update Checker"); private static final HttpClient client = HttpClient.newHttpClient(); - private static boolean apiV2Deprecated = false; + private static boolean modrinthApiV2Deprecated = false; private static boolean allowsUpdateChecks(Mod mod) { return mod.allowsUpdateChecks(); @@ -47,7 +47,7 @@ public static void checkForUpdates() { } public static void checkForCustomUpdates() { - ModMenu.MODS.values().stream().filter(ModrinthUtil::allowsUpdateChecks).forEach(mod -> { + ModMenu.MODS.values().stream().filter(UpdateCheckerUtil::allowsUpdateChecks).forEach(mod -> { UpdateChecker updateChecker = mod.getUpdateChecker(); if (updateChecker == null) { return; @@ -57,12 +57,12 @@ public static void checkForCustomUpdates() { } public static void checkForModrinthUpdates() { - if (apiV2Deprecated) { + if (modrinthApiV2Deprecated) { return; } Map> modHashes = new HashMap<>(); - new ArrayList<>(ModMenu.MODS.values()).stream().filter(ModrinthUtil::allowsUpdateChecks).filter(mod -> mod.getUpdateChecker() == null).forEach(mod -> { + new ArrayList<>(ModMenu.MODS.values()).stream().filter(UpdateCheckerUtil::allowsUpdateChecks).filter(mod -> mod.getUpdateChecker() == null).forEach(mod -> { String modId = mod.getId(); try { @@ -103,7 +103,7 @@ public static void checkForModrinthUpdates() { int status = latestVersionsResponse.statusCode(); LOGGER.debug("Status: " + status); if (status == 410) { - apiV2Deprecated = true; + modrinthApiV2Deprecated = true; LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates."); } else if (status == 200) { JsonObject responseObject = JsonParser.parseString(latestVersionsResponse.body()).getAsJsonObject(); @@ -137,7 +137,7 @@ public static void checkForModrinthUpdates() { } public static void triggerV2DeprecatedToast() { - if (apiV2Deprecated && ModMenuConfig.UPDATE_CHECKER.getValue()) { + if (modrinthApiV2Deprecated && ModMenuConfig.UPDATE_CHECKER.getValue()) { MinecraftClient.getInstance().getToastManager().add(new SystemToast( SystemToast.Type.PERIODIC_NOTIFICATION, Text.translatable("modmenu.modrinth.v2_deprecated.title"), diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java index 3b057ee14..d293d9d34 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/quilt/QuiltMod.java @@ -2,7 +2,7 @@ import com.google.common.collect.Lists; import com.google.common.hash.Hashing; -import com.terraformersmc.modmenu.util.ModrinthUtil; +import com.terraformersmc.modmenu.util.UpdateCheckerUtil; import com.terraformersmc.modmenu.util.mod.fabric.FabricMod; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -15,7 +15,6 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Set; @@ -69,7 +68,7 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set jars = paths.stream().filter(p -> p.toString().toLowerCase(Locale.ROOT).endsWith(".jar")).toList(); @@ -78,7 +77,7 @@ public QuiltMod(net.fabricmc.loader.api.ModContainer fabricModContainer, Set Date: Sat, 9 Mar 2024 10:14:07 +0100 Subject: [PATCH 3/5] Make download link required --- .../java/com/terraformersmc/modmenu/api/UpdateInfo.java | 7 ++----- .../modmenu/gui/widget/DescriptionListWidget.java | 5 +---- src/main/resources/assets/modmenu/lang/en_us.json | 1 - 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java index 98fc89160..fee9c07b7 100644 --- a/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateInfo.java @@ -19,11 +19,8 @@ default Text getUpdateMessage() { } /** - * @return The URL to the mod download or null if there is no download link available. + * @return The URL to the mod download. */ - @Nullable - default String getDownloadLink() { - return null; - } + String getDownloadLink(); } diff --git a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java index 06fed8a3d..b600be890 100644 --- a/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java +++ b/src/main/java/com/terraformersmc/modmenu/gui/widget/DescriptionListWidget.java @@ -40,7 +40,6 @@ public class DescriptionListWidget extends EntryListWidget Date: Sat, 9 Mar 2024 11:37:01 +0100 Subject: [PATCH 4/5] Fix search --- .../java/com/terraformersmc/modmenu/util/mod/ModSearch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java b/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java index 437770f68..95fb0c4b7 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java +++ b/src/main/java/com/terraformersmc/modmenu/util/mod/ModSearch.java @@ -67,7 +67,7 @@ private static int passesFilters(ModsScreen screen, Mod mod, String query) { || deprecated.contains(query) && mod.getBadges().contains(Mod.Badge.DEPRECATED) // Search for deprecated mods || clientside.contains(query) && mod.getBadges().contains(Mod.Badge.CLIENT) // Search for clientside mods || configurable.contains(query) && screen.getModHasConfigScreen().get(modId) // Search for mods that can be configured - || hasUpdate.contains(query) && hasUpdate() // Search for mods that have updates + || hasUpdate.contains(query) && mod.hasUpdate() // Search for mods that have updates ) { return 1; } From 23d8bc81006535b6c9a6ff33658c628a5d3157d0 Mon Sep 17 00:00:00 2001 From: Max Henkel Date: Tue, 2 Apr 2024 09:59:18 +0200 Subject: [PATCH 5/5] Use a separate thread for every custom update checker --- .../modmenu/api/UpdateChecker.java | 6 ++++++ .../modmenu/util/UpdateCheckerThread.java | 17 +++++++++++++++++ .../modmenu/util/UpdateCheckerUtil.java | 10 ++++------ 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java diff --git a/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java index 87ce1886c..1c2aa231e 100644 --- a/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java +++ b/src/main/java/com/terraformersmc/modmenu/api/UpdateChecker.java @@ -2,6 +2,12 @@ public interface UpdateChecker { + /** + * Gets called when ModMenu is checking for updates. + * This is done in a separate thread, so this call can/should be blocking. + * + * @return The update info + */ UpdateInfo checkForUpdates(); } diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java new file mode 100644 index 000000000..67b02b723 --- /dev/null +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerThread.java @@ -0,0 +1,17 @@ +package com.terraformersmc.modmenu.util; + +import com.terraformersmc.modmenu.util.mod.Mod; + +public class UpdateCheckerThread extends Thread { + + protected UpdateCheckerThread(Mod mod, Runnable runnable) { + super(runnable); + setDaemon(true); + setName("Update Checker/%s".formatted(mod.getName())); + } + + public static void run(Mod mod, Runnable runnable) { + new UpdateCheckerThread(mod, runnable).start(); + } + +} diff --git a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java index 6870574e3..509c20600 100644 --- a/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java +++ b/src/main/java/com/terraformersmc/modmenu/util/UpdateCheckerUtil.java @@ -39,11 +39,9 @@ public static void checkForUpdates() { return; } - Util.getMainWorkerExecutor().execute(() -> { - LOGGER.info("Checking mod updates..."); - checkForModrinthUpdates(); - checkForCustomUpdates(); - }); + LOGGER.info("Checking mod updates..."); + Util.getMainWorkerExecutor().execute(UpdateCheckerUtil::checkForModrinthUpdates); + checkForCustomUpdates(); } public static void checkForCustomUpdates() { @@ -52,7 +50,7 @@ public static void checkForCustomUpdates() { if (updateChecker == null) { return; } - mod.setUpdateInfo(updateChecker.checkForUpdates()); + UpdateCheckerThread.run(mod, () -> mod.setUpdateInfo(updateChecker.checkForUpdates())); }); }