Skip to content

Commit

Permalink
Add provided update checkers, loader update checkers (#711)
Browse files Browse the repository at this point in the history
- Add update checkers for Fabric Loader and Quilt Loader
- Add API for mods to provide update checkers for other mods
  • Loading branch information
LostLuma authored Apr 22, 2024
1 parent 00fe1ad commit 77f63a4
Show file tree
Hide file tree
Showing 10 changed files with 444 additions and 28 deletions.
10 changes: 9 additions & 1 deletion src/main/java/com/terraformersmc/modmenu/ModMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public void onInitializeClient() {
ModMenuConfigManager.initializeConfig();
Set<String> modpackMods = new HashSet<>();
Map<String, UpdateChecker> updateCheckers = new HashMap<>();
Map<String, UpdateChecker> providedUpdateCheckers = new HashMap<>();

FabricLoader.getInstance().getEntrypointContainers("modmenu", ModMenuApi.class).forEach(entrypoint -> {
ModMetadata metadata = entrypoint.getProvider().getMetadata();
String modId = metadata.getId();
Expand All @@ -78,6 +80,7 @@ public void onInitializeClient() {
configScreenFactories.put(modId, api.getModConfigScreenFactory());
apiImplementations.add(api);
updateCheckers.put(modId, api.getUpdateChecker());
providedUpdateCheckers.putAll(api.getProvidedUpdateCheckers());
api.attachModpackBadges(modpackMods::add);
} catch (Throwable e) {
LOGGER.error("Mod {} provides a broken implementation of ModMenuApi", modId, e);
Expand All @@ -94,9 +97,14 @@ public void onInitializeClient() {
mod = new FabricMod(modContainer, modpackMods);
}

mod.setUpdateChecker(updateCheckers.get(mod.getId()));
var updateChecker = updateCheckers.get(mod.getId());

if (updateChecker == null) {
updateChecker = providedUpdateCheckers.get(mod.getId());
}

MODS.put(mod.getId(), mod);
mod.setUpdateChecker(updateChecker);
}

checkForUpdates();
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/com/terraformersmc/modmenu/ModMenuModMenuCompat.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
package com.terraformersmc.modmenu;

import com.google.common.collect.ImmutableMap;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import com.terraformersmc.modmenu.api.UpdateChecker;
import com.terraformersmc.modmenu.gui.ModMenuOptionsScreen;
import com.terraformersmc.modmenu.util.mod.fabric.FabricLoaderUpdateChecker;
import com.terraformersmc.modmenu.util.mod.quilt.QuiltLoaderUpdateChecker;

import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.option.OptionsScreen;

import java.util.Map;

public class ModMenuModMenuCompat implements ModMenuApi {

@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ModMenuOptionsScreen::new;
}

@Override
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
return ImmutableMap.of("minecraft", parent -> new OptionsScreen(parent, MinecraftClient.getInstance().options));
return Map.of("minecraft", parent -> new OptionsScreen(parent, MinecraftClient.getInstance().options));
}

@Override
public Map<String, UpdateChecker> getProvidedUpdateCheckers() {
if (ModMenu.runningQuilt) {
return Map.of("quilt_loader", new QuiltLoaderUpdateChecker());
} else {
return Map.of("fabricloader", new FabricLoaderUpdateChecker());
}
}
}
13 changes: 11 additions & 2 deletions src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.terraformersmc.modmenu.api;

import com.google.common.collect.ImmutableMap;
import com.terraformersmc.modmenu.ModMenu;
import com.terraformersmc.modmenu.gui.ModsScreen;
import net.minecraft.client.gui.screen.Screen;
Expand Down Expand Up @@ -66,7 +65,17 @@ default UpdateChecker getUpdateChecker() {
* @return a map of mod ids to screen factories.
*/
default Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
return ImmutableMap.of();
return Map.of();
}

/**
* Used to provide update checkers for other mods. A mod registering its own
* update checker will take priority over any provided ones should both exist.
*
* @return a map of mod ids to update checkers.
*/
default Map<String, UpdateChecker> getProvidedUpdateCheckers() {
return Map.of();
}

/**
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/com/terraformersmc/modmenu/util/HttpUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.terraformersmc.modmenu.util;

import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

import com.terraformersmc.modmenu.ModMenu;

import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;

public class HttpUtil {
private static final String USER_AGENT = buildUserAgent();
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();

private HttpUtil() {}

public static <T> HttpResponse<T> request(HttpRequest.Builder builder, HttpResponse.BodyHandler<T> handler) throws IOException, InterruptedException {
builder.setHeader("User-Agent", USER_AGENT);
return HTTP_CLIENT.send(builder.build(), handler);
}

private static String buildUserAgent() {
String env = ModMenu.devEnvironment ? "/development" : "";
String loader = ModMenu.runningQuilt ? "quilt" : "fabric";

var modMenuVersion = getModMenuVersion();
var minecraftVersion = SharedConstants.getGameVersion().getName();

// -> TerraformersMC/ModMenu/9.1.0 (1.20.3/quilt/development)
return "%s/%s (%s/%s%s)".formatted(ModMenu.GITHUB_REF, modMenuVersion, minecraftVersion, loader, env);
}

private static String getModMenuVersion() {
var container = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID);

if (container.isEmpty()) {
throw new RuntimeException("Unable to find Modmenu's own mod container!");
}

return VersionUtil.removeBuildMetadata(container.get().getMetadata().getVersion().getFriendlyString());
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/terraformersmc/modmenu/util/JsonUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.terraformersmc.modmenu.util;

import java.util.Optional;

import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

public class JsonUtil {
private JsonUtil() {}

public static Optional<String> getString(JsonObject parent, String field) {
if (!parent.has(field)) {
return Optional.empty();
}

var value = parent.get(field);

if (!value.isJsonPrimitive() || !((JsonPrimitive)value).isString()) {
return Optional.empty();
}

return Optional.of(value.getAsString());
}

public static Optional<Boolean> getBoolean(JsonObject parent, String field) {
if (!parent.has(field)) {
return Optional.empty();
}

var value = parent.get(field);

if (!value.isJsonPrimitive() || !((JsonPrimitive)value).isBoolean()) {
return Optional.empty();
}

return Optional.of(value.getAsBoolean());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,19 @@
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
import net.minecraft.util.Util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;

public class UpdateCheckerUtil {
public static final Logger LOGGER = LoggerFactory.getLogger("Mod Menu/Update Checker");

private static final HttpClient client = HttpClient.newHttpClient();
private static boolean modrinthApiV2Deprecated = false;

private static boolean allowsUpdateChecks(Mod mod) {
Expand All @@ -48,10 +47,21 @@ public static void checkForUpdates() {
public static void checkForCustomUpdates() {
ModMenu.MODS.values().stream().filter(UpdateCheckerUtil::allowsUpdateChecks).forEach(mod -> {
UpdateChecker updateChecker = mod.getUpdateChecker();

if (updateChecker == null) {
return;
}
UpdateCheckerThread.run(mod, () -> mod.setUpdateInfo(updateChecker.checkForUpdates()));

UpdateCheckerThread.run(mod, () -> {
var update = updateChecker.checkForUpdates();

if (update == null) {
return;
}

mod.setUpdateInfo(update);
LOGGER.info("Update available for '{}@{}'", mod.getId(), mod.getVersion());
});
});
}

Expand All @@ -77,15 +87,8 @@ public static void checkForModrinthUpdates() {
}
});

String environment = ModMenu.devEnvironment ? "/development" : "";
String primaryLoader = ModMenu.runningQuilt ? "quilt" : "fabric";
List<String> loaders = ModMenu.runningQuilt ? List.of("fabric", "quilt") : List.of("fabric");

String mcVer = SharedConstants.getGameVersion().getName();
String version = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID)
.get().getMetadata().getVersion().getFriendlyString();
final var modMenuVersion = version.split("\\+", 2)[0]; // Strip build metadata for privacy
final var userAgent = "%s/%s (%s/%s%s)".formatted(ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment);
List<String> loaders = ModMenu.runningQuilt ? List.of("fabric", "quilt") : List.of("fabric");

List<UpdateChannel> updateChannels;
UpdateChannel preferredChannel = UpdateChannel.getUserPreference();
Expand All @@ -100,20 +103,17 @@ public static void checkForModrinthUpdates() {

String body = ModMenu.GSON_MINIFIED.toJson(new LatestVersionsFromHashesBody(modHashes.keySet(), loaders, mcVer, updateChannels));

LOGGER.debug("User agent: " + userAgent);
LOGGER.debug("Body: " + body);
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();
.uri(URI.create("https://api.modrinth.com/v2/version_files/update"));

try {
var latestVersionsResponse = client.send(latestVersionsRequest, HttpResponse.BodyHandlers.ofString());
var latestVersionsResponse = HttpUtil.request(latestVersionsRequest, HttpResponse.BodyHandlers.ofString());

int status = latestVersionsResponse.statusCode();
LOGGER.debug("Status: " + status);
LOGGER.debug("Status: {}", status);
if (status == 410) {
modrinthApiV2Deprecated = true;
LOGGER.warn("Modrinth API v2 is deprecated, unable to check for mod updates.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
public final class VersionUtil {
private static final List<String> PREFIXES = List.of("version", "ver", "v");

private VersionUtil() {
return;
}
private VersionUtil() {}

public static String stripPrefix(String version) {
version = version.trim();
Expand All @@ -24,4 +22,8 @@ public static String stripPrefix(String version) {
public static String getPrefixedVersion(String version) {
return "v" + stripPrefix(version);
}

public static String removeBuildMetadata(String version) {
return version.split("\\+")[0];
}
}
Loading

0 comments on commit 77f63a4

Please sign in to comment.