From dd170e7e5cee959cc1e925c2a57a7a70c6c5195b Mon Sep 17 00:00:00 2001 From: FalsePattern <30945458+FalsePattern@users.noreply.github.com> Date: Fri, 10 Jun 2022 00:09:13 +0200 Subject: [PATCH] added update checks and separate internet library --- dependencies.gradle | 1 + repositories.gradle | 4 + .../lib/dependencies/DependencyLoader.java | 106 ++++++----------- .../lib/internal/FalsePatternLib.java | 29 ++++- .../falsepattern/lib/internal/Internet.java | 40 +++++++ .../lib/updates/ModUpdateInfo.java | 19 ++- .../lib/updates/UpdateChecker.java | 109 ++++++++++++++---- 7 files changed, 207 insertions(+), 101 deletions(-) create mode 100644 src/main/java/com/falsepattern/lib/internal/Internet.java diff --git a/dependencies.gradle b/dependencies.gradle index 587c204..49e56ff 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -5,5 +5,6 @@ dependencies { compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") { transitive = false } + compileOnly("com.falsepattern:json:0.4.1") annotationProcessor("org.projectlombok:lombok:1.18.22") } diff --git a/repositories.gradle b/repositories.gradle index aa8bcb1..d265b7a 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -3,4 +3,8 @@ repositories { name = "sponge" url = "https://sponge.falsepattern.com/" } + maven { + name = "mavenpattern" + url = "https://maven.falsepattern.com/" + } } diff --git a/src/main/java/com/falsepattern/lib/dependencies/DependencyLoader.java b/src/main/java/com/falsepattern/lib/dependencies/DependencyLoader.java index 6bed9ec..220614a 100644 --- a/src/main/java/com/falsepattern/lib/dependencies/DependencyLoader.java +++ b/src/main/java/com/falsepattern/lib/dependencies/DependencyLoader.java @@ -1,11 +1,10 @@ package com.falsepattern.lib.dependencies; import com.falsepattern.lib.StableAPI; -import com.falsepattern.lib.internal.CoreLoadingPlugin; -import com.falsepattern.lib.internal.FalsePatternLib; +import com.falsepattern.lib.internal.*; + import java.io.BufferedOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; @@ -14,13 +13,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import javax.net.ssl.HttpsURLConnection; -import com.falsepattern.lib.internal.LibraryConfig; -import lombok.Builder; -import lombok.NonNull; -import lombok.val; -import lombok.var; +import lombok.*; import net.minecraft.launchwrapper.LaunchClassLoader; @@ -36,21 +30,18 @@ public class DependencyLoader { } @Builder - public static void loadLibrary(String loadingModId, - String groupId, - String artifactId, + public static void loadLibrary(@NonNull String loadingModId, + @NonNull String groupId, + @NonNull String artifactId, @NonNull Version minVersion, Version maxVersion, @NonNull Version preferredVersion, String regularSuffix, String devSuffix) { val suffix = FalsePatternLib.isDeveloperEnvironment() ? devSuffix : regularSuffix; + val artifactLogName = String.format("%s:%s:%s%s", groupId, artifactId, preferredVersion, suffix != null ? "-" + suffix : ""); FalsePatternLib.getLog() - .info("Adding library {}:{}:{}{}, requested by mod {}", - groupId, - artifactId, - preferredVersion, - suffix != null ? "-" + suffix : "", + .info("Adding library {}, requested by mod {}", artifactLogName, loadingModId); var artifact = groupId + ":" + artifactId + ":" + suffix; if (loadedLibraries.containsKey(artifact)) { @@ -110,19 +101,11 @@ public class DependencyLoader { addToClasspath(file); loadedLibraries.put(artifact, preferredVersion); FalsePatternLib.getLog() - .info("Library {}:{}:{}{} successfully loaded from disk!", - groupId, - artifactId, - preferredVersion, - (suffix != null) ? ":" + suffix : ""); + .info("Library {} successfully loaded from disk!", artifactLogName); return; } catch (RuntimeException e) { FalsePatternLib.getLog() - .warn("Failed to load library {}:{}:{}{} from file! Redownloading...", - groupId, - artifactId, - preferredVersion, - (suffix != null) ? ":" + suffix : ""); + .warn("Failed to load library {} from file! Redownloading...", artifactLogName); if (!file.delete()) { FalsePatternLib.getLog().fatal("Failed to delete file {}", file); throw new RuntimeException("Failed to delete file " + file); @@ -131,9 +114,9 @@ public class DependencyLoader { } if (!LibraryConfig.ENABLE_LIBRARY_DOWNLOADS) { val errorMessage = "Failed to load library " + groupId + ":" + artifactId + ":" + preferredVersion + - ((suffix != null) ? ":" + suffix : "") + ": FalsePatternLib library downloading has " + - "been disabled in the config, and the library is not present on disk! Requested by mod: " + - loadingModId; + ((suffix != null) ? ":" + suffix : "") + ": " + Tags.MODNAME + + " library downloading has been disabled in the config, and the library is not present " + + "on disk! Requested by mod: " + loadingModId; FalsePatternLib.getLog().fatal(errorMessage); throw new IllegalStateException(errorMessage); } @@ -148,39 +131,24 @@ public class DependencyLoader { artifactId, preferredVersion, mavenJarName)); - - val connection = (HttpsURLConnection) url.openConnection(); - connection.setConnectTimeout(1500); - connection.setReadTimeout(1500); - connection.setRequestProperty("User-Agent", "FalsePatternLib Downloader"); - if (connection.getResponseCode() != 200) { + String finalRepo = repo; + Internet.connect(url, (ex) -> { FalsePatternLib.getLog() - .info("Artifact {}:{}:{}{} was not found on repo {}", - groupId, - artifactId, - preferredVersion, - (suffix != null) ? ":" + suffix : "", - repo); - connection.disconnect(); - continue; - } - FalsePatternLib.getLog() - .info("Downloading {}:{}:{}{} from {}", - groupId, - artifactId, - preferredVersion, - (suffix != null) ? ":" + suffix : "", - repo); - download(connection.getInputStream(), file); - FalsePatternLib.getLog() - .info("Downloaded {}:{}:{}{}", - groupId, - artifactId, - preferredVersion, - (suffix != null) ? ":" + suffix : ""); - loadedLibraries.put(artifact, preferredVersion); - loadedLibraryMods.put(artifact, loadingModId); - addToClasspath(file); + .info("Artifact {} could not be downloaded from repo {}: {}", + artifactLogName, + finalRepo, + ex.getMessage()); + }, (input) -> { + FalsePatternLib.getLog() + .info("Downloading {} from {}", + artifactLogName, + finalRepo); + download(input, file); + FalsePatternLib.getLog().info("Downloaded {}", artifactLogName); + loadedLibraries.put(artifact, preferredVersion); + loadedLibraryMods.put(artifact, loadingModId); + addToClasspath(file); + }); return; } catch (IOException ignored) { } @@ -202,19 +170,11 @@ public class DependencyLoader { } } - private static void download(InputStream is, File target) throws IOException { + @SneakyThrows + private static void download(InputStream is, File target) { if (target.exists()) { return; } - - var bytesRead = 0; - - val fileOutput = new BufferedOutputStream(Files.newOutputStream(target.toPath())); - byte[] smallBuffer = new byte[4096]; - while ((bytesRead = is.read(smallBuffer)) >= 0) { - fileOutput.write(smallBuffer, 0, bytesRead); - } - fileOutput.close(); - is.close(); + Internet.transferAndClose(is, new BufferedOutputStream(Files.newOutputStream(target.toPath()))); } } diff --git a/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java b/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java index eb7b795..1b78588 100644 --- a/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java +++ b/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java @@ -2,6 +2,8 @@ package com.falsepattern.lib.internal; import com.falsepattern.lib.config.ConfigException; import com.falsepattern.lib.config.ConfigurationManager; +import com.falsepattern.lib.updates.ModUpdateInfo; +import com.falsepattern.lib.updates.UpdateChecker; import com.falsepattern.lib.util.ResourceUtil; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; @@ -15,10 +17,15 @@ import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import lombok.Getter; import lombok.SneakyThrows; +import lombok.val; import net.minecraft.launchwrapper.Launch; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + /** * Utility class used by FalsePatternLib's internal code. This can change between versions without notice, so do not use * this in your code! @@ -30,6 +37,8 @@ public class FalsePatternLib extends DummyModContainer { @Getter private static final boolean developerEnvironment = (boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment"); + private Future> updateCheckFuture; + public FalsePatternLib() { super(MetadataCollection.from(ResourceUtil.getResourceFromJar("/mcmod.info", FalsePatternLib.class), Tags.MODID) .getMetadataForId(Tags.MODID, null)); @@ -48,8 +57,26 @@ public class FalsePatternLib extends DummyModContainer { } @Subscribe - public void postInit(FMLPostInitializationEvent e) { + public void preInit(FMLPreInitializationEvent e) { + if (LibraryConfig.ENABLE_UPDATE_CHECKER) { + getLog().info("Launching asynchronous update check. I'll check back on it during postInit."); + updateCheckFuture = UpdateChecker.fetchUpdatesAsync("https://falsepattern.com/mc/versions.json"); + } + } + @Subscribe + public void postInit(FMLPostInitializationEvent e) { + if (updateCheckFuture != null && !updateCheckFuture.isCancelled()) { + try { + val updates = updateCheckFuture.get(); + if (updates != null) + for (val update: updates) + update.log(getLog()); + } catch (InterruptedException | ExecutionException ex) { + getLog().warn("Error while checking updates", ex); + } + + } } @SuppressWarnings("UnstableApiUsage") diff --git a/src/main/java/com/falsepattern/lib/internal/Internet.java b/src/main/java/com/falsepattern/lib/internal/Internet.java new file mode 100644 index 0000000..a3421ae --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/Internet.java @@ -0,0 +1,40 @@ +package com.falsepattern.lib.internal; + +import lombok.val; +import lombok.var; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.function.Consumer; + +public class Internet { + public static void connect(URL URL, Consumer onError, Consumer onSuccess) { + try { + val connection = (HttpURLConnection) URL.openConnection(); + connection.setConnectTimeout(3500); + connection.setReadTimeout(5000); + connection.setRequestProperty("User-Agent", "FalsePatternLib Internet Connector"); + if (connection.getResponseCode() != 200) { + onError.accept(new Exception("HTTP response code " + connection.getResponseCode())); + } else { + onSuccess.accept(connection.getInputStream()); + } + connection.disconnect(); + } catch (Exception e) { + onError.accept(e); + } + } + + + public static void transferAndClose(InputStream is, OutputStream target) throws IOException { + var bytesRead = 0; + + byte[] smallBuffer = new byte[4096]; + while ((bytesRead = is.read(smallBuffer)) >= 0) { + target.write(smallBuffer, 0, bytesRead); + } + target.close(); + is.close(); + } +} diff --git a/src/main/java/com/falsepattern/lib/updates/ModUpdateInfo.java b/src/main/java/com/falsepattern/lib/updates/ModUpdateInfo.java index 2b363a0..1a42604 100644 --- a/src/main/java/com/falsepattern/lib/updates/ModUpdateInfo.java +++ b/src/main/java/com/falsepattern/lib/updates/ModUpdateInfo.java @@ -1,11 +1,22 @@ package com.falsepattern.lib.updates; import lombok.Data; +import lombok.NonNull; +import org.apache.logging.log4j.Logger; @Data public class ModUpdateInfo { - public final String modID; - public final String currentVersion; - public final String latestVersion; - public final String updateURL; + @NonNull public final String modID; + @NonNull public final String currentVersion; + @NonNull public final String latestVersion; + @NonNull public final String updateURL; + + public void log(Logger logger) { + logger.info("Updates are available for mod {}: Currently installed version is {}, " + + "latest available version is {}. Update URL: {}", + modID, + currentVersion, + latestVersion, + updateURL.isEmpty() ? "unavailable": updateURL); + } } diff --git a/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java b/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java index 7a6837e..a92c3f2 100644 --- a/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java +++ b/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java @@ -1,27 +1,30 @@ package com.falsepattern.lib.updates; +import com.falsepattern.json.node.JsonNode; import com.falsepattern.lib.dependencies.DependencyLoader; import com.falsepattern.lib.dependencies.SemanticVersion; import com.falsepattern.lib.internal.FalsePatternLib; +import com.falsepattern.lib.internal.Internet; import com.falsepattern.lib.internal.LibraryConfig; import com.falsepattern.lib.internal.Tags; +import cpw.mods.fml.common.Loader; import lombok.val; -import lombok.var; -import java.io.BufferedOutputStream; -import java.io.File; +import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; +import java.net.MalformedURLException; import java.net.URL; -import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class UpdateChecker { - private static AtomicBoolean jsonLibraryLoaded = new AtomicBoolean(false); + private static AtomicInteger jsonLibraryLoaded = new AtomicInteger(0); private static final ExecutorService asyncExecutor = Executors.newSingleThreadExecutor((runnable) -> { Thread thread = new Thread(runnable); thread.setDaemon(true); @@ -64,22 +67,82 @@ public class UpdateChecker { if (!LibraryConfig.ENABLE_UPDATE_CHECKER) { return null; } - if (!jsonLibraryLoaded.get()) { - DependencyLoader.addMavenRepo("https://maven.falsepattern.com/"); - try { - DependencyLoader.builder() - .groupId("com.falsepattern") - .artifactId("json") - .minVersion(new SemanticVersion(0, 4, 0)) - .maxVersion(new SemanticVersion(0, Integer.MAX_VALUE, Integer.MAX_VALUE)) - .build(); - } catch (Exception e) { - FalsePatternLib.getLog().error("Failed to load json library for update checker!", e); - return null; - } - jsonLibraryLoaded.set(true); + URL URL; + try { + URL = new URL(url); + } catch (MalformedURLException e) { + FalsePatternLib.getLog().error("Invalid URL: {}", url, e); + return null; } - //TODO - return null; + switch (jsonLibraryLoaded.get()) { + case 0: + DependencyLoader.addMavenRepo("https://maven.falsepattern.com/"); + try { + DependencyLoader.builder() + .loadingModId(Tags.MODID) + .groupId("com.falsepattern") + .artifactId("json") + .minVersion(new SemanticVersion(0, 4, 0)) + .maxVersion(new SemanticVersion(0, Integer.MAX_VALUE, Integer.MAX_VALUE)) + .preferredVersion(new SemanticVersion(0, 4, 1)) + .build(); + } catch (Exception e) { + FalsePatternLib.getLog().error("Failed to load json library for update checker!", e); + jsonLibraryLoaded.set(-1); + return null; + } + jsonLibraryLoaded.set(1); + break; + case -1: + return null; + } + AtomicReference loadedData = new AtomicReference<>(null); + Internet.connect(URL, (ex) -> { + FalsePatternLib.getLog().warn("Failed to check for updates from URL {}", url, ex); + }, (input) -> { + val data = new ByteArrayOutputStream(); + try { + Internet.transferAndClose(input, data); + } catch (IOException e) { + throw new RuntimeException(e); + } + loadedData.set(data.toString()); + }); + if (loadedData.get() == null) return null; + val result = new ArrayList(); + val parsed = JsonNode.parse(loadedData.get()); + List modList; + if (parsed.isList()) { + modList = parsed.getJavaList(); + } else { + modList = Collections.singletonList(parsed); + } + val installedMods = Loader.instance().getIndexedModList(); + for (val node: modList) { + if (!node.isObject()) continue; + if (!node.containsKey("modid")) continue; + if (!node.containsKey("latestVersion")) continue; + val modid = node.getString("modid"); + if (!installedMods.containsKey(modid)) continue; + val mod = installedMods.get(modid); + val latestVersionsNode = node.get("latestVersion"); + List latestVersions; + if (latestVersionsNode.isString()) { + latestVersions = Collections.singletonList(latestVersionsNode.stringValue()); + } else if (latestVersionsNode.isList()) { + latestVersions = new ArrayList<>(); + for (val version: latestVersionsNode.getJavaList()) { + if (!version.isString()) continue; + latestVersions.add(version.stringValue()); + } + } else { + continue; + } + val currentVersion = mod.getVersion(); + if (latestVersions.contains(currentVersion)) continue; + val updateURL = node.containsKey("updateURL") && node.get("updateURL").isString() ? node.getString("updateURL") : ""; + result.add(new ModUpdateInfo(modid, currentVersion, latestVersions.get(0), updateURL)); + } + return result; } }