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 committed Apr 23, 2024
1 parent 46b699f commit be2e94b
Show file tree
Hide file tree
Showing 10 changed files with 454 additions and 23 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 initClient() {
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 initClient() {
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 initClient() {
mod = new FabricMod(modContainer, modpackMods);
}

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

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

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

checkForUpdates();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
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.Minecraft;
import net.minecraft.client.gui.screen.options.OptionsScreen;

import java.util.Map;

public class ModMenuModMenuCompat implements ModMenuApi {

@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ModMenuOptionsScreen::new;
Expand All @@ -20,4 +23,13 @@ public ConfigScreenFactory<?> getModConfigScreenFactory() {
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
return ImmutableMap.of("minecraft", parent -> new OptionsScreen(parent, Minecraft.getInstance().options));
}

@Override
public Map<String, UpdateChecker> getProvidedUpdateCheckers() {
if (ModMenu.runningQuilt) {
return ImmutableMap.of("quilt_loader", new QuiltLoaderUpdateChecker());
} else {
return ImmutableMap.of("fabricloader", new FabricLoaderUpdateChecker());
}
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ default Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
return ImmutableMap.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 ImmutableMap.of();
}

/**
* Used to mark mods with a badge indicating that they are
* provided by a modpack.
Expand Down
47 changes: 47 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,47 @@
package com.terraformersmc.modmenu.util;

import java.io.IOException;
import java.util.Optional;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

import com.terraformersmc.modmenu.ModMenu;

import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;

public class HttpUtil {
private static final String USER_AGENT = buildUserAgent();
private static final CloseableHttpClient HTTP_CLIENT = HttpClientBuilder.create().build();

private HttpUtil() {}

public static HttpResponse request(RequestBuilder builder) throws IOException {
builder.setHeader("User-Agent", USER_AGENT);
return HTTP_CLIENT.execute(builder.build());
}

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

String modMenuVersion = getModMenuVersion(ModMenu.MOD_ID);
String minecraftVersion = getModMenuVersion("minecraft");

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

private static String getModMenuVersion(String modId) {
Optional<ModContainer> container = FabricLoader.getInstance().getModContainer(modId);

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

return VersionUtil.removeBuildMetadata(container.get().getMetadata().getVersion().getFriendlyString());
}
}
39 changes: 39 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,39 @@
package com.terraformersmc.modmenu.util;

import java.util.Optional;

import com.google.gson.JsonElement;
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();
}

JsonElement 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();
}

JsonElement 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 @@ -7,17 +7,15 @@
import com.terraformersmc.modmenu.ModMenu;
import com.terraformersmc.modmenu.api.UpdateChannel;
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.ModrinthUpdateInfo;
import net.fabricmc.loader.api.FabricLoader;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -30,7 +28,6 @@
public class UpdateCheckerUtil {
public static final Logger LOGGER = LogManager.getLogger("Mod Menu/Update Checker");

private static final HttpClient client = HttpClientBuilder.create().build();
private static boolean modrinthApiV2Deprecated = false;

private static boolean allowsUpdateChecks(Mod mod) {
Expand All @@ -51,10 +48,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, () -> {
UpdateInfo update = updateChecker.checkForUpdates();

if (update == null) {
return;
}

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

Expand All @@ -80,16 +88,10 @@ public static void checkForModrinthUpdates() {
}
});

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

String mcVer = FabricLoader.getInstance().getModContainer("minecraft").get()
.getMetadata().getVersion().getFriendlyString();
String version = FabricLoader.getInstance().getModContainer(ModMenu.MOD_ID)
.get().getMetadata().getVersion().getFriendlyString();
final String modMenuVersion = version.split("\\+", 2)[0]; // Strip build metadata for privacy
final String userAgent = String.format("%s/%s (%s/%s%s)", ModMenu.GITHUB_REF, modMenuVersion, mcVer, primaryLoader, environment);

List<UpdateChannel> updateChannels;
UpdateChannel preferredChannel = UpdateChannel.getUserPreference();
Expand All @@ -104,18 +106,15 @@ 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);

try {
HttpUriRequest latestVersionsRequest = RequestBuilder.post()
RequestBuilder latestVersionsRequest = RequestBuilder.post()
.setEntity(new StringEntity(body))
.addHeader("User-Agent", userAgent)
.addHeader("Content-Type", "application/json")
.setUri(URI.create("https://api.modrinth.com/v2/version_files/update"))
.build();
.setUri(URI.create("https://api.modrinth.com/v2/version_files/update"));

HttpResponse latestVersionsResponse = client.execute(latestVersionsRequest);
HttpResponse latestVersionsResponse = HttpUtil.request(latestVersionsRequest);

int status = latestVersionsResponse.getStatusLine().getStatusCode();
LOGGER.debug("Status: " + status);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
public final class VersionUtil {
private static final List<String> PREFIXES = Arrays.asList("version", "ver", "v");

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

public static String stripPrefix(String version) {
version = version.trim();
Expand All @@ -25,4 +23,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 be2e94b

Please sign in to comment.