feat: implement new, flow-based MDS
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/push/docs Pipeline failed

This commit is contained in:
Johannes Frohnmeyer 2024-06-22 19:58:01 +02:00
parent 082a4f3b9c
commit 4945381030
Signed by: Johannes
GPG Key ID: E76429612C2929F4
37 changed files with 601 additions and 58 deletions

View File

@ -11,7 +11,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
public class GList { public class GList {
public static <T, TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> List<T> read(Reader reader, ThrowingFunction<Reader, T, TEx> read) throws TEx { public static <T, TEx extends Exception, Reader extends SerializeReader<TEx, ?>> List<T> read(Reader reader, ThrowingFunction<Reader, T, TEx> read) throws TEx {
if (reader.isLenient() && reader.peek() != Token.BEGIN_ARRAY) return List.of(read.apply(reader)); if (reader.isLenient() && reader.peek() != Token.BEGIN_ARRAY) return List.of(read.apply(reader));
reader.beginArray(); reader.beginArray();
List<T> res = new LinkedList<>(); List<T> res = new LinkedList<>();

View File

@ -9,6 +9,7 @@ import java.io.IOException;
public class InceptumEnvironmentInitializer { public class InceptumEnvironmentInitializer {
public static void initialize() throws IOException { public static void initialize() throws IOException {
HttpClient.scheduleOnlineCheck();
HotswapLoggerFinder.updateAllStrategies(InceptumEnvironmentInitializer::defaultFactory); HotswapLoggerFinder.updateAllStrategies(InceptumEnvironmentInitializer::defaultFactory);
HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION); HttpClient.setUserAgent("jfmods/inceptum/" + BuildMetadata.VERSION);
InceptumConfig.load(); InceptumConfig.load();

View File

@ -5,8 +5,7 @@ import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus; import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
import java.awt.*; import java.awt.*;
import java.io.File; import java.io.*;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
@ -46,8 +45,12 @@ public class Utils {
} }
} }
public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException { public static FileSystem openZipFile(Path zip, boolean create) throws IOException {
return JFiles.openZipFile(zip, create, SYSTEM_LOADER); try {
return JFiles.openZipFile(zip, create, SYSTEM_LOADER);
} catch (URISyntaxException e) {
throw new IOException("Could not access file system", e);
}
} }
@SuppressWarnings("unused") // Called through reflection from wrapper @SuppressWarnings("unused") // Called through reflection from wrapper

View File

@ -1,5 +1,5 @@
[versions] [versions]
jf-commons = "1.7-SNAPSHOT" jf-commons = "2.0.0-SNAPSHOT"
annotations = "24.0.1" annotations = "24.0.1"
lwjgl = "3.3.2" lwjgl = "3.3.2"
imgui = "1.86.10" imgui = "1.86.10"
@ -34,6 +34,8 @@ javagi-adw = { module = "io.github.jwharm.javagi:adw", version.ref = "javagi" }
commons = { module = "io.gitlab.jfronny:commons", version.ref = "jf-commons" } commons = { module = "io.gitlab.jfronny:commons", version.ref = "jf-commons" }
commons-http-client = { module = "io.gitlab.jfronny:commons-http-client", version.ref = "jf-commons" } commons-http-client = { module = "io.gitlab.jfronny:commons-http-client", version.ref = "jf-commons" }
commons-http-server = { module = "io.gitlab.jfronny:commons-http-server", version.ref = "jf-commons" } commons-http-server = { module = "io.gitlab.jfronny:commons-http-server", version.ref = "jf-commons" }
commons-flow = { module = "io.gitlab.jfronny:commons-flow", version.ref = "jf-commons" }
commons-flow-backend-unsafe = { module = "io.gitlab.jfronny:commons-flow-backend-unsafe", version.ref = "jf-commons" }
commons-io = { module = "io.gitlab.jfronny:commons-io", version.ref = "jf-commons" } commons-io = { module = "io.gitlab.jfronny:commons-io", version.ref = "jf-commons" }
commons-logger = { module = "io.gitlab.jfronny:commons-logger", version.ref = "jf-commons" } commons-logger = { module = "io.gitlab.jfronny:commons-logger", version.ref = "jf-commons" }
commons-serialize-json = { module = "io.gitlab.jfronny:commons-serialize-json", version.ref = "jf-commons" } commons-serialize-json = { module = "io.gitlab.jfronny:commons-serialize-json", version.ref = "jf-commons" }

View File

@ -50,7 +50,7 @@ public class ModCommand extends Command {
return; return;
} }
System.out.println("Scanning installed mods, this might take a while"); System.out.println("Scanning installed mods, this might take a while");
instance.mds().runOnce(ScanStage.UPDATECHECK, (path, mod) -> { instance.mds().runOnce(ScanStage.ALL, (path, mod) -> {
boolean hasSources = !mod.getMetadata().sources().isEmpty(); boolean hasSources = !mod.getMetadata().sources().isEmpty();
boolean updatable = hasSources && mod.getMetadata().sources().values().stream().anyMatch(Optional::isPresent); boolean updatable = hasSources && mod.getMetadata().sources().values().stream().anyMatch(Optional::isPresent);
if (filterUpdatable && !updatable) return; if (filterUpdatable && !updatable) return;

View File

@ -6,8 +6,13 @@ import org.gnome.gtk.Application
class InstanceSettingsWindow(val app: Application?, val instance: Instance) : SettingsWindow(app) { class InstanceSettingsWindow(val app: Application?, val instance: Instance) : SettingsWindow(app) {
init { init {
val claim = instance.mds.focus()
addTab(GeneralTab(this), "instance.settings.general", "preferences-other-symbolic") addTab(GeneralTab(this), "instance.settings.general", "preferences-other-symbolic")
addTab(ModsTab(this), "instance.settings.mods", "package-x-generic-symbolic") addTab(ModsTab(this), "instance.settings.mods", "package-x-generic-symbolic")
addTab(ExportTab(this), "instance.settings.export", "send-to-symbolic") addTab(ExportTab(this), "instance.settings.export", "send-to-symbolic")
onCloseRequest {
claim.close()
false
}
} }
} }

View File

@ -85,7 +85,7 @@ public class AddModWindow extends Window {
ImGui.text(mod.description()); ImGui.text(mod.description());
ImGui.tableNextColumn(); ImGui.tableNextColumn();
boolean alreadyPresent = false; boolean alreadyPresent = false;
for (Mod mdsMod : instance.getMods(ScanStage.CROSSREFERENCE)) { for (Mod mdsMod : instance.mds().getMods()) {
alreadyPresent = mdsMod.getMetadata().sources().keySet().stream() alreadyPresent = mdsMod.getMetadata().sources().keySet().stream()
.anyMatch(s -> s instanceof ModrinthModSource ms && ms.getModId().equals(projectId)); .anyMatch(s -> s instanceof ModrinthModSource ms && ms.getModId().equals(projectId));
if (alreadyPresent) if (alreadyPresent)

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.inceptum.imgui.window.edit; package io.gitlab.jfronny.inceptum.imgui.window.edit;
import imgui.ImGui; import imgui.ImGui;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.imgui.control.Tab; import io.gitlab.jfronny.inceptum.imgui.control.Tab;
import io.gitlab.jfronny.inceptum.imgui.window.GuiUtil; import io.gitlab.jfronny.inceptum.imgui.window.GuiUtil;
import io.gitlab.jfronny.inceptum.imgui.window.Window; import io.gitlab.jfronny.inceptum.imgui.window.Window;
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv;
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
public class InstanceEditWindow extends Window { public class InstanceEditWindow extends Window {
protected final Instance instance; protected final Instance instance;
protected final Closeable focus;
private final List<Tab> tabs; private final List<Tab> tabs;
protected boolean reDownload = false; protected boolean reDownload = false;
protected boolean lastTabWasMods = false; protected boolean lastTabWasMods = false;
@ -19,6 +23,7 @@ public class InstanceEditWindow extends Window {
super(instance.getName() + " - Edit"); super(instance.getName() + " - Edit");
this.instance = instance; this.instance = instance;
this.instance.mds().start(); this.instance.mds().start();
this.focus = instance.mds().focus();
this.tabs = List.of( this.tabs = List.of(
new GeneralTab(this), new GeneralTab(this),
new ArgumentsTab(this), new ArgumentsTab(this),
@ -46,6 +51,11 @@ public class InstanceEditWindow extends Window {
@Override @Override
public void close() { public void close() {
super.close(); super.close();
try {
focus.close();
} catch (IOException e) {
Utils.LOGGER.error("Could not release focus on MDS", e);
}
if (reDownload) { if (reDownload) {
GuiUtil.reload(instance); GuiUtil.reload(instance);
} }

View File

@ -6,4 +6,6 @@ plugins {
dependencies { dependencies {
api(projects.common) api(projects.common)
api(libs.commons.http.server) // required for launcher-gtk for some reason api(libs.commons.http.server) // required for launcher-gtk for some reason
api(libs.commons.flow)
api(libs.commons.flow.backend.unsafe)
} }

View File

@ -6,11 +6,11 @@ import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount; import io.gitlab.jfronny.inceptum.launcher.api.account.MicrosoftAccount;
public class MicrosoftAccountAdapter { public class MicrosoftAccountAdapter {
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serialize(MicrosoftAccount value, Writer writer) throws TEx, MalformedDataException { public static <TEx extends Exception, Writer extends SerializeWriter<TEx, ?>> void serialize(MicrosoftAccount value, Writer writer) throws TEx, MalformedDataException {
GC_MicrosoftAccountMeta.serialize(value == null ? null : value.toMeta(), writer); GC_MicrosoftAccountMeta.serialize(value == null ? null : value.toMeta(), writer);
} }
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> MicrosoftAccount deserialize(Reader reader) throws TEx, MalformedDataException { public static <TEx extends Exception, Reader extends SerializeReader<TEx, ?>> MicrosoftAccount deserialize(Reader reader) throws TEx, MalformedDataException {
MicrosoftAccountMeta meta = GC_MicrosoftAccountMeta.deserialize(reader); MicrosoftAccountMeta meta = GC_MicrosoftAccountMeta.deserialize(reader);
return meta == null ? null : new MicrosoftAccount(meta); return meta == null ? null : new MicrosoftAccount(meta);
} }

View File

@ -11,11 +11,11 @@ import java.util.List;
import java.util.Set; import java.util.Set;
public class MinecraftArgumentAdapter { public class MinecraftArgumentAdapter {
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serialize(MinecraftArgument rules, Writer writer) throws TEx { public static <TEx extends Exception, Writer extends SerializeWriter<TEx, ?>> void serialize(MinecraftArgument rules, Writer writer) throws TEx {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> MinecraftArgument deserialize(Reader reader) throws TEx, MalformedDataException { public static <TEx extends Exception, Reader extends SerializeReader<TEx, ?>> MinecraftArgument deserialize(Reader reader) throws TEx, MalformedDataException {
if (reader.peek() == Token.STRING) return new MinecraftArgument(Set.of(reader.nextString())); if (reader.peek() == Token.STRING) return new MinecraftArgument(Set.of(reader.nextString()));
Rules rules = null; Rules rules = null;
List<String> value = null; List<String> value = null;

View File

@ -10,13 +10,13 @@ import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import java.util.Optional; import java.util.Optional;
public class ModMetaSourcesAdapter { public class ModMetaSourcesAdapter {
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serialize(Sources value, Writer writer) throws TEx, MalformedDataException { public static <TEx extends Exception, Writer extends SerializeWriter<TEx, ?>> void serialize(Sources value, Writer writer) throws TEx, MalformedDataException {
writer.beginArray(); writer.beginArray();
for (ModSource source : value.keySet()) GC_ModSource.serialize(source, writer); for (ModSource source : value.keySet()) GC_ModSource.serialize(source, writer);
writer.endArray(); writer.endArray();
} }
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> Sources deserialize(Reader reader) throws TEx, MalformedDataException { public static <TEx extends Exception, Reader extends SerializeReader<TEx, ?>> Sources deserialize(Reader reader) throws TEx, MalformedDataException {
reader.beginArray(); reader.beginArray();
Sources sources = new Sources(); Sources sources = new Sources();
while (reader.hasNext()) { while (reader.hasNext()) {

View File

@ -10,7 +10,7 @@ import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
public class ModSourceAdapter { public class ModSourceAdapter {
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serialize(ModSource src, Writer writer) throws TEx { public static <TEx extends Exception, Writer extends SerializeWriter<TEx, ?>> void serialize(ModSource src, Writer writer) throws TEx {
writer.beginObject(); writer.beginObject();
switch (src) { switch (src) {
case ModrinthModSource mo -> writer.name("type").value("modrinth") case ModrinthModSource mo -> writer.name("type").value("modrinth")
@ -41,7 +41,7 @@ public class ModSourceAdapter {
writer.endObject(); writer.endObject();
} }
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> ModSource deserialize(Reader reader) throws TEx, MalformedDataException { public static <TEx extends Exception, Reader extends SerializeReader<TEx, ?>> ModSource deserialize(Reader reader) throws TEx, MalformedDataException {
String type = null; String type = null;
String mr$id = null; String mr$id = null;

View File

@ -7,11 +7,11 @@ import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.inceptum.launcher.model.mojang.Rules; import io.gitlab.jfronny.inceptum.launcher.model.mojang.Rules;
public class RulesAdapter { public class RulesAdapter {
public static <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serialize(Rules rules, Writer writer) throws TEx { public static <TEx extends Exception, Writer extends SerializeWriter<TEx, ?>> void serialize(Rules rules, Writer writer) throws TEx {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public static <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> Rules deserialize(Reader reader) throws TEx, MalformedDataException { public static <TEx extends Exception, Reader extends SerializeReader<TEx, ?>> Rules deserialize(Reader reader) throws TEx, MalformedDataException {
boolean valid = true; boolean valid = true;
reader.beginArray(); reader.beginArray();
while (reader.hasNext()) { while (reader.hasNext()) {
@ -49,7 +49,7 @@ public class RulesAdapter {
throw new MalformedDataException("Unexpected action in argument: " + actionType); throw new MalformedDataException("Unexpected action in argument: " + actionType);
} }
if (hasFeatures) valid = false; if (hasFeatures) valid = false;
if (osName != null && !OSUtils.TYPE.getMojName().equals(osName)) valid = false; if (osName != null && !OSUtils.TYPE.mojName.equals(osName)) valid = false;
if (osVersion != null && !System.getProperty("os.version").matches(osVersion)) valid = false; if (osVersion != null && !System.getProperty("os.version").matches(osVersion)) valid = false;
if (actionType.equals("disallow")) valid = !valid; if (actionType.equals("disallow")) valid = !valid;
} }

View File

@ -98,18 +98,17 @@ public record ModMeta(
return res; return res;
} }
public boolean updateCheck(String gameVersion) { public boolean crossReference() {
boolean modrinth = false; boolean modrinth = false;
boolean curseforge = false; boolean curseforge = false;
for (ModSource source : sources.keySet().toArray(ModSource[]::new)) { for (ModSource source : sources.keySet().toArray(ModSource[]::new)) {
if (source instanceof ModrinthModSource) modrinth = true; if (source instanceof ModrinthModSource) modrinth = true;
if (source instanceof CurseforgeModSource) curseforge = true; if (source instanceof CurseforgeModSource) curseforge = true;
checkAndAddSource(source, gameVersion);
} }
boolean changed = false; boolean changed = false;
if (!modrinth) { if (!modrinth) {
try { try {
checkAndAddSource(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id()), gameVersion); sources.put(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id()), Optional.empty());
changed = true; changed = true;
} catch (IOException e) { } catch (IOException e) {
// not found // not found
@ -120,7 +119,7 @@ public record ModMeta(
FingerprintMatchesResponse.Result cf = CurseforgeApi.checkFingerprint(murmur2); FingerprintMatchesResponse.Result cf = CurseforgeApi.checkFingerprint(murmur2);
if (!cf.exactMatches().isEmpty()) { if (!cf.exactMatches().isEmpty()) {
FingerprintMatchesResponse.Result.Match f = cf.exactMatches().getFirst(); FingerprintMatchesResponse.Result.Match f = cf.exactMatches().getFirst();
checkAndAddSource(new CurseforgeModSource(f.id(), f.file().id()), gameVersion); sources.put(new CurseforgeModSource(f.id(), f.file().id()), Optional.empty());
changed = true; changed = true;
} }
} catch (IOException | URISyntaxException e) { } catch (IOException | URISyntaxException e) {
@ -130,6 +129,12 @@ public record ModMeta(
return changed; return changed;
} }
public void updateCheck(String gameVersion) {
for (ModSource source : sources.keySet().toArray(ModSource[]::new)) {
checkAndAddSource(source, gameVersion);
}
}
private void checkAndAddSource(ModSource source, String gameVersion) { private void checkAndAddSource(ModSource source, String gameVersion) {
try { try {
sources.put(source, source.getUpdate(gameVersion)); sources.put(source, source.getUpdate(gameVersion));

View File

@ -9,7 +9,6 @@ import io.gitlab.jfronny.inceptum.launcher.util.ProcessState;
import io.gitlab.jfronny.inceptum.launcher.util.gitignore.IgnoringWalk; import io.gitlab.jfronny.inceptum.launcher.util.gitignore.IgnoringWalk;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*; import java.nio.file.*;
import java.util.Objects; import java.util.Objects;
@ -37,7 +36,7 @@ public abstract class Exporter<Manifest> {
Manifest manifest = generateManifests(root, instance, instance.meta().instanceVersion); Manifest manifest = generateManifests(root, instance, instance.meta().instanceVersion);
if (instance.isFabric()) { if (instance.isFabric()) {
state.incrementStep("Adding mods"); state.incrementStep("Adding mods");
addMods(root, instance, instance.getMods(ScanStage.CROSSREFERENCE).stream().filter(mod -> { addMods(root, instance, instance.completeModsScan(ScanStage.CROSSREFERENCE).stream().filter(mod -> {
if (!mod.isEnabled()) return false; if (!mod.isEnabled()) return false;
state.updateStep(mod.getName()); state.updateStep(mod.getName());
return true; return true;
@ -58,9 +57,6 @@ public abstract class Exporter<Manifest> {
} }
state.incrementStep("Cleaning up"); state.incrementStep("Cleaning up");
Files.walkFileTree(root, new CleanupFileVisitor()); Files.walkFileTree(root, new CleanupFileVisitor());
} catch (URISyntaxException se) {
// Can only be thrown by openZipFile, the target file therefore cannot exist
throw new IOException("Could not open export path", se);
} catch (Throwable t) { } catch (Throwable t) {
if (Files.exists(exportPath)) Files.delete(exportPath); if (Files.exists(exportPath)) Files.delete(exportPath);
throw t; throw t;

View File

@ -28,8 +28,6 @@ public class Importers {
return importer.importPack(fs.getPath("."), state); return importer.importPack(fs.getPath("."), state);
} }
} }
} catch (URISyntaxException e) {
throw new IOException("Could not open zip", e);
} }
throw new IOException("Could not import pack: unsupported format"); throw new IOException("Could not import pack: unsupported format");
} }

View File

@ -75,8 +75,10 @@ public record Instance(String id, Path path, InstanceMeta meta, ModsDirScanner m
return path.resolve("config"); return path.resolve("config");
} }
public Set<Mod> getMods(ScanStage stage) throws IOException { public Set<Mod> completeModsScan(ScanStage stage) throws IOException {
mds.runOnce(stage, R::nop); try (var focus = mds.focus()) {
mds.runOnce(stage, R::nop);
}
return mds.getMods(); return mds.getMods();
} }

View File

@ -19,7 +19,6 @@ import io.gitlab.jfronny.inceptum.launcher.util.VersionInfoLibraryResolver;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.*; import java.nio.file.*;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -101,7 +100,7 @@ public class InstanceLauncher {
// Fabric imods // Fabric imods
if (instance.isFabric()) { if (instance.isFabric()) {
StringBuilder fabricAddMods = new StringBuilder("-Dfabric.addMods="); StringBuilder fabricAddMods = new StringBuilder("-Dfabric.addMods=");
for (Mod mod : instance.getMods(ScanStage.DOWNLOAD)) { for (Mod mod : instance.completeModsScan(ScanStage.DOWNLOAD)) {
if (mod.isEnabled() && mod.getNeedsInject()) { if (mod.isEnabled() && mod.getNeedsInject()) {
fabricAddMods.append(mod.getJarPath()); fabricAddMods.append(mod.getJarPath());
fabricAddMods.append(File.pathSeparatorChar); fabricAddMods.append(File.pathSeparatorChar);
@ -197,7 +196,7 @@ public class InstanceLauncher {
return line.substring(linePrefix.length()); return line.substring(linePrefix.length());
} }
} }
} catch (IOException | URISyntaxException e) { } catch (IOException e) {
throw new LaunchException("IO Exception while trying to identify entrypoint", e); throw new LaunchException("IO Exception while trying to identify entrypoint", e);
} }
throw new LaunchException("Could not identify entrypoint"); throw new LaunchException("Could not identify entrypoint");

View File

@ -21,29 +21,31 @@ import java.util.stream.Collectors;
public class MdsMod extends Mod { public class MdsMod extends Mod {
private final ProtoInstance instance; private final ProtoInstance instance;
private final Path imodPath; private final Path imodPath;
private final Path initialJarPath;
private final ModMeta meta; private final ModMeta meta;
private @NotNull ScanStage scanStage = ScanStage.DISCOVER; private @NotNull ScanStage scanStage = ScanStage.DISCOVER;
private @Nullable DownloadAux downloadAux = null; private @Nullable DownloadAux downloadAux = null;
public MdsMod(ProtoInstance instance, Path imodPath, ModMeta meta) { public MdsMod(ProtoInstance instance, Path imodPath, Path initialJarPath, ModMeta meta) {
this.instance = instance; this.instance = instance;
this.imodPath = imodPath; this.imodPath = imodPath;
this.initialJarPath = initialJarPath;
this.meta = meta; this.meta = meta;
} }
private record DownloadAux(Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) {} private record DownloadAux(Path imodPath, Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) {}
public void markDownloaded(Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) { public void markDownloaded(Path imodPath, Path jarPath, boolean managedJar, @Nullable FabricModJson fmj) {
this.downloadAux = new DownloadAux(jarPath, managedJar, fmj); this.downloadAux = new DownloadAux(imodPath, jarPath, managedJar, fmj);
this.scanStage = ScanStage.DOWNLOAD; this.scanStage = ScanStage.DOWNLOAD;
} }
public void markCrossReferenced() { public void markCrossReferenced() {
this.scanStage = ScanStage.CROSSREFERENCE; if (!this.scanStage.contains(ScanStage.CROSSREFERENCE)) this.scanStage = ScanStage.CROSSREFERENCE;
} }
public void markUpdateChecked() { public void markUpdateChecked() {
this.scanStage = ScanStage.UPDATECHECK; if (!this.scanStage.contains(ScanStage.UPDATECHECK)) this.scanStage = ScanStage.UPDATECHECK;
} }
private Optional<FabricModJson> getFmj() { private Optional<FabricModJson> getFmj() {
@ -80,12 +82,12 @@ public class MdsMod extends Mod {
@Override @Override
public Path getJarPath() { public Path getJarPath() {
return requireDownload().jarPath; return downloadAux == null ? initialJarPath : requireDownload().jarPath;
} }
@Override @Override
public Path getMetadataPath() { public Path getMetadataPath() {
return imodPath; return downloadAux == null ? imodPath : requireDownload().imodPath;
} }
@Override @Override

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds; package io.gitlab.jfronny.inceptum.launcher.system.mds;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta; import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.mds.flow.FlowMds;
import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMds; import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMds;
import io.gitlab.jfronny.inceptum.launcher.system.mds.threaded.ThreadedMds;
import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser; import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser;
import java.io.Closeable; import java.io.Closeable;
@ -14,18 +14,20 @@ import java.util.function.BiConsumer;
public interface ModsDirScanner extends Closeable { public interface ModsDirScanner extends Closeable {
static ModsDirScanner get(Path modsDir, InstanceMeta meta) throws IOException { static ModsDirScanner get(Path modsDir, InstanceMeta meta) throws IOException {
if (Files.exists(modsDir)) return ThreadedMds.get(modsDir, meta); if (Files.exists(modsDir)) return FlowMds.get(modsDir, meta);//ThreadedMds.get(modsDir, meta);
return new NoopMds(GameVersionParser.getGameVersion(meta.gameVersion)); return new NoopMds(GameVersionParser.getGameVersion(meta.gameVersion));
} }
static void closeAll() { static void closeAll() {
ThreadedMds.closeAll(); FlowMds.closeAll();//ThreadedMds.closeAll();
} }
boolean isComplete(ScanStage stage); boolean isComplete(ScanStage stage);
void start(); void start();
Closeable focus();
String getGameVersion(); String getGameVersion();
Set<Mod> getMods() throws IOException; Set<Mod> getMods() throws IOException;

View File

@ -20,9 +20,13 @@ public enum ScanStage implements Comparable<ScanStage> {
/** /**
* The mod(s) have been checked for updates * The mod(s) have been checked for updates
*/ */
UPDATECHECK; UPDATECHECK,
/**
* The mod(s) have been scanned in all stages
*/
ALL;
public boolean isComplete(ScanStage stage) { public boolean contains(ScanStage stage) {
return ordinal() >= stage.ordinal(); return ordinal() >= stage.ordinal();
} }

View File

@ -0,0 +1,195 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.io.JFiles;
import io.gitlab.jfronny.commons.ref.R;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMod;
import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser;
import io.gitlab.jfronny.inceptum.launcher.util.VoidClaimPool;
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.nio.file.StandardWatchEventKinds.*;
public class FlowMds implements ModsDirScanner {
private static final Map<Path, FlowMds> SCANNERS = new HashMap<>();
private boolean disposed = false;
private final MdsThreadFactory factory = new MdsThreadFactory("mds");
private final ProtoInstance instance;
private final WatchService service;
private final Thread th;
private final Map<Path, Mod> descriptions = new HashMap<>();
private final Set<Path> scannedPaths = new HashSet<>();
private FlowMds(Path modsDir, InstanceMeta instance) throws IOException {
this.instance = new ProtoInstance(modsDir, this, instance);
this.th = factory.newThread(this::scanTaskInternal, modsDir.getParent().getFileName().toString());
this.service = FileSystems.getDefault().newWatchService();
modsDir.register(service, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);
}
public static FlowMds get(Path modsDir, InstanceMeta instance) throws IOException {
if (SCANNERS.containsKey(modsDir)) {
FlowMds mds = SCANNERS.get(modsDir);
if (mds.instance.meta().equals(instance)) return mds;
mds.close();
}
FlowMds mds = new FlowMds(modsDir, instance);
SCANNERS.put(modsDir, mds);
return mds;
}
@Override
public boolean isComplete(ScanStage stage) {
if (!Files.isDirectory(instance.modsDir())) return true;
try {
for (Path path : JFiles.list(instance.modsDir())) {
Mod mod = descriptions.get(path);
if (mod == null || mod.getScanStage().ordinal() < stage.ordinal()) return false;
}
} catch (IOException e) {
Utils.LOGGER.error("Could not list files in mods dir", e);
}
return true;
}
@Override
public void start() {
if (!th.isAlive()) th.start();
}
@Override
public VoidClaimPool.Claim focus() {
return factory.focusClaim.claim();
}
@Override
public String getGameVersion() {
return GameVersionParser.getGameVersion(instance.meta().gameVersion);
}
@Override
public Set<Mod> getMods() throws IOException {
Set<Mod> mods = new TreeSet<>();
if (Files.isDirectory(instance.modsDir())) {
for (Path path : JFiles.list(instance.modsDir())) {
if (ModPath.isImod(path) && Files.exists(ModPath.trimImod(path)))
continue;
mods.add(get(path));
}
}
return mods;
}
@Override
public Mod get(Path path) {
if (!Files.isRegularFile(path)) return null;
if (!descriptions.containsKey(path)) return new NoopMod(path); // not yet scanned
return descriptions.get(path);
}
@Override
public void invalidate(Path path) {
descriptions.remove(path);
scannedPaths.remove(path);
}
@Override
public boolean hasScanned(Path path) {
return scannedPaths.contains(path) || scannedPaths.contains(ModPath.appendImod(path));
}
private void scanTaskInternal() {
while (!disposed) {
runOnce(ScanStage.ALL, R::nop);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private Set<Path> getToScan() throws IOException {
if (descriptions.isEmpty()) return Set.copyOf(JFiles.list(instance.modsDir()));
Set<Path> toScan = new HashSet<>();
WatchKey key = service.poll();
if (key != null) {
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context() instanceof Path p) {
toScan.add(instance.modsDir().resolve(p));
}
}
if (!key.reset()) Utils.LOGGER.error("Could not reset watch key");
}
JFiles.listTo(instance.modsDir(), path -> {
if (!descriptions.containsKey(path)) toScan.add(path);
});
return toScan;
}
@Override
public void runOnce(ScanStage targetStage, BiConsumer<Path, Mod> discovered) {
try {
if (!Files.isDirectory(instance.modsDir())) {
return;
}
MdsPipeline pipeline = new MdsPipeline();
if (targetStage.contains(ScanStage.DOWNLOAD)) {
pipeline.addTask(ScanStage.DOWNLOAD, new MdsDownloadTask(instance));
}
if (targetStage.contains(ScanStage.CROSSREFERENCE)) {
pipeline.addTask(ScanStage.CROSSREFERENCE, new MdsCrossReferenceTask(instance));
}
if (targetStage.contains(ScanStage.UPDATECHECK)) {
pipeline.addTask(ScanStage.UPDATECHECK, new MdsUpdateTask(instance, getGameVersion()));
}
pipeline.addTask(ScanStage.NONE, (path, mod) -> {
scannedPaths.add(path);
descriptions.put(path, mod);
if (mod != null) discovered.accept(path, mod);
});
var futures1 = pipeline.run(factory, getToScan(), new MdsDiscoverTask(instance, getGameVersion()));
var futures2 = pipeline.run(factory, descriptions.entrySet().stream().map(Tuple::from).collect(Collectors.toSet()));
for (CompletableFuture<Void> future : Stream.concat(futures1.stream(), futures2.stream()).toList()) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
Utils.LOGGER.error("Could not scan file for mod info", e);
}
}
} catch (IOException e) {
Utils.LOGGER.error("Could not scan file for mod info", e);
}
}
public static void closeAll() {
for (FlowMds value : SCANNERS.values().toArray(FlowMds[]::new)) {
try {
value.close();
} catch (IOException e) {
Utils.LOGGER.error("Could not close MDS", e);
}
}
MdsThreadFactory.scheduler.shutdown();
}
@Override
public void close() throws IOException {
disposed = true;
service.close();
SCANNERS.remove(instance.modsDir());
}
}

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.GC_ModMeta;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import java.io.IOException;
public record MdsCrossReferenceTask(ProtoInstance instance) implements ThrowingConsumer<MdsMod, IOException> {
@Override
public void accept(MdsMod mod) throws IOException {
if (mod.getScanStage().contains(ScanStage.CROSSREFERENCE)) return;
if (HttpClient.wasOnline()) {
if (mod.getMetadata().crossReference()) {
GC_ModMeta.serialize(mod.getMetadata(), mod.getMetadataPath(), GsonPreset.CONFIG);
}
mod.markCrossReferenced();
}
}
}

View File

@ -0,0 +1,34 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.throwable.ThrowingFunction;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.GC_ModMeta;
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.ModMeta;
import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMod;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public record MdsDiscoverTask(ProtoInstance instance, String gameVersion) implements ThrowingFunction<Path, Mod, IOException> {
@Override
public Mod apply(Path file) throws IOException {
if (!Files.exists(file)) return null;
if (Files.isDirectory(file)) return null; // Directories are not supported
if (ModPath.isJar(file)) return discover(file, ModPath.appendImod(file));
else if (ModPath.isImod(file)) return discover(ModPath.trimImod(file), file);
else return new NoopMod(file);
}
private Mod discover(Path jarPath, Path imodPath) throws IOException {
ModMeta meta;
if (Files.exists(imodPath)) meta = GC_ModMeta.deserialize(imodPath, GsonPreset.CONFIG);
else {
meta = ModMeta.fromJar(jarPath);
GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG);
}
return new MdsMod(instance, imodPath, jarPath, meta);
}
}

View File

@ -0,0 +1,53 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.inceptum.common.GsonPreset;
import io.gitlab.jfronny.inceptum.common.Utils;
import io.gitlab.jfronny.inceptum.launcher.model.fabric.FabricModJson;
import io.gitlab.jfronny.inceptum.launcher.model.fabric.GC_FabricModJson;
import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource;
import java.io.IOException;
import java.nio.file.*;
public record MdsDownloadTask(ProtoInstance instance) implements ThrowingConsumer<MdsMod, IOException> {
@Override
public void accept(MdsMod mod) throws IOException {
if (mod.getScanStage().contains(ScanStage.DOWNLOAD)) return;
ModSource selectedSource = null;
for (ModSource source : mod.getMetadata().sources().keySet()) {
if (!Files.exists(source.getJarPath()) && HttpClient.wasOnline()) source.download();
selectedSource = source;
}
Path imodPath = mod.getMetadataPath();
Path jarPath = mod.getJarPath();
boolean managed = false;
if (selectedSource != null) {
if (jarPath.startsWith(instance.modsDir()) && Files.exists(jarPath)) {
if (Files.exists(selectedSource.getJarPath())) {
Files.delete(jarPath);
Path newImod = imodPath.getParent().resolve(selectedSource.getShortName() + ModPath.EXT_IMOD);
Files.move(imodPath, newImod);
imodPath = newImod;
jarPath = selectedSource.getJarPath();
managed = true;
}
} else {
jarPath = selectedSource.getJarPath();
managed = true;
}
} else if (!Files.exists(jarPath)) throw new IOException("Mod has no jar and no sources");
FabricModJson fmj;
try (FileSystem fs = Utils.openZipFile(jarPath, false)) {
Path fmjPath = fs.getPath("fabric.mod.json");
if (Files.exists(fmjPath)) fmj = GC_FabricModJson.deserialize(fmjPath, GsonPreset.API);
else fmj = null;
}
mod.markDownloaded(imodPath, jarPath, managed, fmj);
}
}

View File

@ -0,0 +1,68 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.throwable.*;
import io.gitlab.jfronny.commons.tuple.Tuple;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class MdsPipeline {
private final Set<Tuple<ScanStage, ThrowingConsumer<Tuple<Path, Mod>, IOException>>> functions = new HashSet<>();
public void addTask(ScanStage stage, ThrowingConsumer<MdsMod, IOException> task) {
functions.add(Tuple.of(stage, tuple -> {
if (tuple.right() instanceof MdsMod mmod) task.accept(mmod);
}));
}
public void addTask(ScanStage stage, ThrowingBiConsumer<Path, MdsMod, IOException> task) {
functions.add(Tuple.of(stage, tuple -> {
if (tuple.right() instanceof MdsMod mmod) task.accept(tuple.left(), mmod);
}));
}
private void work(MdsThreadFactory factory, Tuple<Path, Mod> tuple, Queue<Tuple<ScanStage, ThrowingConsumer<Tuple<Path, Mod>, IOException>>> tasks, Consumer<Throwable> fail) {
if (tasks.isEmpty()) return;
Tuple<ScanStage, ThrowingConsumer<Tuple<Path, Mod>, IOException>> task = tasks.poll();
factory.newThread(MdsThreadFactory.prioritize(
task.right()
.compose(() -> tuple)
.andThen(() -> {
work(factory, tuple, tasks, fail);
}).addHandler(fail),
task.left()
)).start();
}
public List<CompletableFuture<Void>> run(MdsThreadFactory factory, Set<Path> paths, ThrowingFunction<Path, Mod, IOException> seed) {
return paths.stream().map(s -> {
CompletableFuture<Void> finished = new CompletableFuture<>();
Queue<Tuple<ScanStage, ThrowingConsumer<Tuple<Path, Mod>, IOException>>> taskQueue = new LinkedList<>(functions);
taskQueue.add(Tuple.of(ScanStage.NONE, tuple -> finished.complete(null)));
Consumer<Throwable> fail = finished::completeExceptionally;
factory.newThread(MdsThreadFactory.prioritize(() -> {
seed.andThen(mod -> {
work(factory, Tuple.of(s, mod), taskQueue, fail);
}).addHandler(fail).accept(s);
}, ScanStage.DISCOVER)).start();
return finished;
}).toList();
}
public List<CompletableFuture<Void>> run(MdsThreadFactory factory, Set<Tuple<Path, Mod>> mods) {
return mods.stream().map(s -> {
CompletableFuture<Void> finished = new CompletableFuture<>();
Queue<Tuple<ScanStage, ThrowingConsumer<Tuple<Path, Mod>, IOException>>> taskQueue = new LinkedList<>(functions);
taskQueue.add(Tuple.of(ScanStage.NONE, tuple -> finished.complete(null)));
Consumer<Throwable> fail = finished::completeExceptionally;
factory.newThread(MdsThreadFactory.prioritize(() -> {
work(factory, s, taskQueue, fail);
}, ScanStage.DISCOVER)).start();
return finished;
}).toList();
}
}

View File

@ -0,0 +1,69 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.flow.*;
import io.gitlab.jfronny.inceptum.launcher.system.mds.ScanStage;
import io.gitlab.jfronny.inceptum.launcher.util.VoidClaimPool;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class MdsThreadFactory implements ThreadFactory {
protected static final ThreadPoolExecutor scheduler = DefaultSchedulers.createPriorityScheduler();
private static final AtomicInteger poolNumber = new AtomicInteger(1);
public final VoidClaimPool focusClaim = new VoidClaimPool(this::focus, this::defocus);
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
private final Set<OwnedThread> ownedRunnables = ConcurrentHashMap.newKeySet();
public MdsThreadFactory(String name) {
this.namePrefix = name + "-" + poolNumber.getAndIncrement() + "-";
}
public static Runnable prioritize(Runnable runnable, ScanStage stage) {
return PrioritizedRunnable.of(runnable, 5 - stage.ordinal());
}
@Override
public Thread newThread(@NotNull Runnable runnable) {
return newThread(runnable, null);
}
public Thread newThread(@NotNull Runnable runnable, @Nullable String name) {
int priority = runnable instanceof PrioritizedRunnable pr ? pr.getPriority() : 0;
OwnedThread ownedRunnable = new OwnedThread(runnable, new Thread[1], priority, this);
Thread t = ScheduledVirtualThreadBuilder.ofVirtual(scheduler)
.name(this.namePrefix + this.threadNumber.getAndIncrement() + (name == null ? "": "-" + name))
.unstarted(ownedRunnable);
ownedRunnable.pfThread[0] = t;
ownedRunnables.add(ownedRunnable);
ScheduledVirtualThreadBuilder.setPriority(t, priority);
return t;
}
record OwnedThread(Runnable runnable, Thread[] pfThread, int basePriority, MdsThreadFactory pool) implements Runnable {
int priority(boolean focus) {
return focus ? basePriority + 5 : basePriority;
}
@Override
public void run() {
try {
runnable.run();
} finally {
pool.ownedRunnables.remove(this);
}
}
}
private void focus() {
ownedRunnables.forEach(r -> ScheduledVirtualThreadBuilder.setPriority(r.pfThread[0], r.priority(true)));
}
private void defocus() {
ownedRunnables.forEach(r -> ScheduledVirtualThreadBuilder.setPriority(r.pfThread[0], r.priority(false)));
}
}

View File

@ -0,0 +1,18 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.flow;
import io.gitlab.jfronny.commons.http.client.HttpClient;
import io.gitlab.jfronny.commons.throwable.ThrowingConsumer;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import java.io.IOException;
public record MdsUpdateTask(ProtoInstance instance, String gameVersion) implements ThrowingConsumer<MdsMod, IOException> {
@Override
public void accept(MdsMod mod) throws IOException {
if (mod.getScanStage().contains(ScanStage.UPDATECHECK)) return;
if (HttpClient.wasOnline()) {
mod.getMetadata().updateCheck(gameVersion);
mod.markUpdateChecked();
}
}
}

View File

@ -1,7 +1,10 @@
package io.gitlab.jfronny.inceptum.launcher.system.mds.noop; package io.gitlab.jfronny.inceptum.launcher.system.mds.noop;
import io.gitlab.jfronny.commons.ref.R;
import io.gitlab.jfronny.inceptum.launcher.system.mds.*; import io.gitlab.jfronny.inceptum.launcher.system.mds.*;
import io.gitlab.jfronny.inceptum.launcher.util.VoidClaimPool;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Set; import java.util.Set;
@ -21,6 +24,11 @@ public record NoopMds(String gameVersion) implements ModsDirScanner {
public void start() { public void start() {
} }
@Override
public Closeable focus() {
return R::nop;
}
@Override @Override
public String getGameVersion() { public String getGameVersion() {
return gameVersion; return gameVersion;

View File

@ -48,7 +48,7 @@ public class NoopMod extends Mod {
@Override @Override
public ScanStage getScanStage() { public ScanStage getScanStage() {
return ScanStage.UPDATECHECK; return ScanStage.DISCOVER;
} }
@Override @Override

View File

@ -33,19 +33,20 @@ public record FileScanTask(ProtoInstance instance, Path file, BiConsumer<Path, M
private void discover(Path jarPath, Path imodPath) throws IOException, URISyntaxException { private void discover(Path jarPath, Path imodPath) throws IOException, URISyntaxException {
boolean managed = false; boolean managed = false;
ModMeta meta; ModMeta meta;
Path initialJarPath = jarPath;
if (Files.exists(imodPath)) meta = GC_ModMeta.deserialize(imodPath, GsonPreset.CONFIG); if (Files.exists(imodPath)) meta = GC_ModMeta.deserialize(imodPath, GsonPreset.CONFIG);
else { else {
meta = ModMeta.fromJar(jarPath); meta = ModMeta.fromJar(jarPath);
GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG); GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG);
} }
boolean modified = false; boolean modified = false;
if (meta.updateCheck(gameVersion)) { if (meta.crossReference()) {
GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG); GC_ModMeta.serialize(meta, imodPath, GsonPreset.CONFIG);
modified = true; modified = true;
} }
meta.updateCheck(gameVersion);
ModSource selectedSource = null; ModSource selectedSource = null;
for (ModSource source : meta.sources().keySet()) { for (ModSource source : meta.sources().keySet()) {
source.getUpdate(gameVersion);
if (!Files.exists(source.getJarPath())) source.download(); if (!Files.exists(source.getJarPath())) source.download();
selectedSource = source; selectedSource = source;
} }
@ -69,8 +70,9 @@ public record FileScanTask(ProtoInstance instance, Path file, BiConsumer<Path, M
else fmj = null; else fmj = null;
} }
MdsMod result = new MdsMod(instance, imodPath, meta); MdsMod result = new MdsMod(instance, imodPath, initialJarPath, meta);
result.markDownloaded(jarPath, managed, fmj); result.markDownloaded(imodPath, jarPath, managed, fmj);
result.markCrossReferenced();
result.markUpdateChecked(); result.markUpdateChecked();
discovered.accept(imodPath, result); discovered.accept(imodPath, result);
} }

View File

@ -9,6 +9,7 @@ import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath;
import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMod; import io.gitlab.jfronny.inceptum.launcher.system.mds.noop.NoopMod;
import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser; import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser;
import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.*; import java.nio.file.*;
import java.util.*; import java.util.*;
@ -63,6 +64,11 @@ public class ThreadedMds implements ModsDirScanner {
if (!th.isAlive()) th.start(); if (!th.isAlive()) th.start();
} }
@Override
public Closeable focus() {
return R::nop;
}
@Override @Override
public String getGameVersion() { public String getGameVersion() {
return GameVersionParser.getGameVersion(instance.meta().gameVersion); return GameVersionParser.getGameVersion(instance.meta().gameVersion);

View File

@ -37,9 +37,6 @@ public class DownloadClientStep implements Step {
try (FileSystem fs = Utils.openZipFile(p, false)) { try (FileSystem fs = Utils.openZipFile(p, false)) {
Files.copy(fs.getPath("META-INF", "versions", minecraftVersion, "server-" + minecraftVersion + ".jar"), Files.copy(fs.getPath("META-INF", "versions", minecraftVersion, "server-" + minecraftVersion + ".jar"),
serverPath); serverPath);
} catch (URISyntaxException e) {
Files.delete(p);
throw new IOException("Could not open bundler zip", e);
} }
Files.delete(p); Files.delete(p);
} else { } else {

View File

@ -13,8 +13,8 @@ public class VersionInfoLibraryResolver {
Set<ArtifactInfo> artifacts = new LinkedHashSet<>(); Set<ArtifactInfo> artifacts = new LinkedHashSet<>();
for (VersionInfo.Library library : version.libraries) { for (VersionInfo.Library library : version.libraries) {
if (library.rules() != null && !library.rules().allow()) continue; if (library.rules() != null && !library.rules().allow()) continue;
if (library.downloads().classifiers() != null && library.natives() != null && library.natives().containsKey(OSUtils.TYPE.getMojName())) { if (library.downloads().classifiers() != null && library.natives() != null && library.natives().containsKey(OSUtils.TYPE.mojName)) {
artifacts.add(new ArtifactInfo(library.downloads().classifiers().get(library.natives().get(OSUtils.TYPE.getMojName())), true)); artifacts.add(new ArtifactInfo(library.downloads().classifiers().get(library.natives().get(OSUtils.TYPE.mojName)), true));
} }
if (library.downloads().artifact() == null) { if (library.downloads().artifact() == null) {
Utils.LOGGER.info("Null library artifact @ " + library.name()); Utils.LOGGER.info("Null library artifact @ " + library.name());

View File

@ -0,0 +1,39 @@
package io.gitlab.jfronny.inceptum.launcher.util;
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class VoidClaimPool {
private final AtomicInteger content = new AtomicInteger(0);
private final Runnable onClaim;
private final Runnable onRelease;
public VoidClaimPool(Runnable onClaim, Runnable onRelease) {
this.onClaim = onClaim;
this.onRelease = onRelease;
}
public Claim claim() {
return new Claim();
}
public boolean isEmpty() {
return content.get() == 0;
}
public class Claim implements Closeable {
private final AtomicBoolean active = new AtomicBoolean(true);
private Claim() {
if (content.getAndIncrement() == 0) onClaim.run();
}
@Override
public void close() {
if (!active.getAndSet(false))
throw new UnsupportedOperationException("Cannot release claim that is already released");
if (content.decrementAndGet() == 0) onRelease.run();
}
}
}

View File

@ -30,4 +30,5 @@ module io.gitlab.jfronny.inceptum.launcher {
requires static org.jetbrains.annotations; requires static org.jetbrains.annotations;
requires static io.gitlab.jfronny.commons.serialize.generator.annotations; requires static io.gitlab.jfronny.commons.serialize.generator.annotations;
requires io.gitlab.jfronny.commons.serialize; requires io.gitlab.jfronny.commons.serialize;
requires io.gitlab.jfronny.commons.flow;
} }