package io.gitlab.jfronny.inceptum.common; import io.gitlab.jfronny.commons.OSUtils; import io.gitlab.jfronny.inceptum.common.api.MavenApi; import io.gitlab.jfronny.inceptum.common.model.inceptum.*; import io.gitlab.jfronny.inceptum.common.model.maven.*; import org.jetbrains.annotations.Nullable; import org.xml.sax.SAXException; import javax.xml.stream.XMLStreamException; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; public class Updater { private static final String ARTIFACTS_URL = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/artifacts/"; private static final String STABLE_URL = "https://pages.frohnmeyer-wds.de/JfMods/Inceptum/stable/"; public static final String PROJECT_MAVEN = "https://maven.frohnmeyer-wds.de/artifacts/"; public static UpdateMetadata getUpdate() { return Updater.check(InceptumConfig.channel, true, channel -> { Utils.LOGGER.error("No stable version was found, switching to experimental channel"); InceptumConfig.channel = channel; InceptumConfig.saveConfig(); }); } public static void update(UpdateMetadata source, boolean relaunch) throws IOException, URISyntaxException { if (Runtime.version().feature() < source.jvm) { throw new OutdatedException("A newer JVM version is required for the current build of Inceptum. Please update!"); } else if (source.wrapperVersion > BuildMetadata.WRAPPER_VERSION) { throw new OutdatedException("The current build of Inceptum requires a newer wrapper version. Please update!"); } else if (source.wrapperVersion < BuildMetadata.WRAPPER_VERSION) { throw new OutdatedException("The current build of Inceptum requires an older wrapper version. Please update!"); } Utils.LOGGER.info("Downloading version " + source.version); WrapperConfig config = new WrapperConfig(); config.natives = new HashMap<>(); config.libraries = new LinkedHashSet<>(); config.repositories = new LinkedHashSet<>(source.repositories); source.natives.forEach((k, v) -> config.natives.put(k, new LinkedHashSet<>(v))); DependencyNode node = downloadLibrary(source.repositories, "io.gitlab.jfronny.inceptum:launcher-dist:" + source.version, config.libraries); Utils.LOGGER.info("Downloaded Dependencies:\n" + node); List currentLibraries = new LinkedList<>(config.libraries); if (source.natives.containsKey(Utils.getCurrentFlavor())) { Set natives = new LinkedHashSet<>(); for (String lib : source.natives.get(Utils.getCurrentFlavor())){ downloadLibrary(source.repositories, lib, natives); } currentLibraries.addAll(natives); config.natives.put(Utils.getCurrentFlavor(), natives); } GC_WrapperConfig.write(MetaHolder.WRAPPER_CONFIG_PATH, config); if (relaunch) { Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { new ProcessBuilder(OSUtils.getJvmBinary(), "-cp", buildClasspath(currentLibraries.stream()) .map(Path::toString) .collect(Collectors.joining("" + File.pathSeparatorChar)) ).inheritIO().start(); } catch (IOException e) { Utils.LOGGER.error("Could not relaunch", e); } })); } } public static List getLaunchClasspath(WrapperConfig wrapperConfig) throws IOException, URISyntaxException { Set natives = wrapperConfig.natives.get(Utils.getCurrentFlavor()); if (natives == null) natives = new LinkedHashSet<>(); Set libs = wrapperConfig.libraries; if (libs == null) libs = new LinkedHashSet<>(); boolean configChanged = false; for (String lib : libs) { Path p = artifactToPath(lib); if (!Files.exists(p)) { configChanged = true; downloadLibrary(wrapperConfig.repositories, lib, libs); } } for (String lib : natives) { Path p = artifactToPath(lib); if (!Files.exists(p)) { configChanged = true; downloadLibrary(wrapperConfig.repositories, lib, natives); } } if (configChanged) GC_WrapperConfig.write(MetaHolder.WRAPPER_CONFIG_PATH, wrapperConfig); return buildClasspath(Stream.concat(libs.stream(), natives.stream())).toList(); } private static Stream buildClasspath(Stream libraries) { return libraries.map(Updater::artifactToPath); } private static DependencyNode downloadLibrary(Set repositories, final String artifact, Set libraries) throws IOException, URISyntaxException { List exceptions = new LinkedList<>(); for (String repository : Stream.concat(Stream.of(PROJECT_MAVEN), repositories.stream()).toList()) { Pom pom; try { pom = MavenApi.getPom(repository, artifact); } catch (IOException | URISyntaxException | XMLStreamException | SAXException e) { exceptions.add(new Exception("Could not download artifact from " + repository, e)); continue; } Set dependencies = new LinkedHashSet<>(); if (pom.dependencies != null) { for (MavenDependency dependency : pom.dependencies) { String mvnName = dependency.groupId + ":" + dependency.artifactId + ":" + dependency.version; dependencies.add(downloadLibrary(repositories, mvnName, libraries)); } } MavenApi.downloadLibrary(repository, pom); libraries.add(artifact); return new DependencyNode(pom, dependencies); } IOException exception = new IOException("Could not find any repository containing the artifact " + artifact + " (searched: " + String.join(", ", repositories) + ")"); for (Exception e : exceptions) { exception.addSuppressed(e); } throw exception; } private static Path artifactToPath(String artifact) { return MetaHolder.LIBRARIES_DIR.resolve(MavenApi.mavenNotationToJarPath(artifact)).toAbsolutePath(); } public static @Nullable UpdateMetadata check(UpdateChannel channel, boolean versionCompare, Consumer channelInvalid) { try { UpdateMetadata experimental = Net.downloadObject(ARTIFACTS_URL + "version.json", GC_UpdateMetadata::read); UpdateMetadata stable = null; try { stable = Net.downloadObject(STABLE_URL + "version.json", GC_UpdateMetadata::read); } catch (Throwable ignored) {} if (stable == null && channel == UpdateChannel.Stable) { channel = UpdateChannel.CI; channelInvalid.accept(channel); } UpdateMetadata info = switch (channel) { case CI -> experimental; case Stable -> stable; }; if (info.jvm > Runtime.version().feature()) { //TODO visual indication for this Utils.LOGGER.error("A newer JVM is required to use the latest inceptum version. Please update!"); return null; } if (versionCompare) { Utils.LOGGER.info("Latest version is " + info.version + ", current is " + BuildMetadata.VERSION); if (BuildMetadata.BUILD_TIME >= info.buildTime) { Utils.LOGGER.info("Up-to-date"); return null; } } return info; } catch (IOException e) { Utils.LOGGER.error("Could not check for updates", e); } return null; } public static String getShadowJarUrl(UpdateChannel channel) { return switch (channel) { case CI -> ARTIFACTS_URL; case Stable -> STABLE_URL; } + "/Inceptum-" + Utils.getCurrentFlavor() + ".jar"; } }