feat: implement new, flow-based MDS
This commit is contained in:
parent
082a4f3b9c
commit
4945381030
|
@ -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<>();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user