Use thread pool for MDS file scanning
This commit is contained in:
parent
76301dc765
commit
3814da2013
|
@ -11,7 +11,7 @@ import io.gitlab.jfronny.inceptum.model.mojang.MinecraftArgument;
|
|||
import io.gitlab.jfronny.inceptum.model.mojang.Rules;
|
||||
import io.gitlab.jfronny.inceptum.util.ConfigHolder;
|
||||
import io.gitlab.jfronny.inceptum.util.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.util.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.util.api.McApi;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
|
|||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.util.ConfigHolder;
|
||||
import io.gitlab.jfronny.inceptum.util.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.util.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.util.api.account.AccountManager;
|
||||
import io.gitlab.jfronny.inceptum.frontend.gui.Window;
|
||||
|
|
|
@ -4,9 +4,10 @@ import io.gitlab.jfronny.inceptum.frontend.cli.BaseInstanceCommand;
|
|||
import io.gitlab.jfronny.inceptum.frontend.cli.Command;
|
||||
import io.gitlab.jfronny.inceptum.frontend.cli.CommandArgs;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.IWModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModSource;
|
||||
import io.gitlab.jfronny.inceptum.util.ModManager;
|
||||
import io.gitlab.jfronny.inceptum.util.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -111,7 +112,7 @@ public class ModCommand extends Command {
|
|||
}
|
||||
for (Path mod : mods) {
|
||||
try {
|
||||
ModManager.delete(new ModsDirScanner.IWModDescription(mod), instancePath.resolve("mods"), mds);
|
||||
ModManager.delete(new IWModDescription(mod), instancePath.resolve("mods"), mds);
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not delete " + mod, e);
|
||||
return;
|
||||
|
|
|
@ -8,13 +8,14 @@ import io.gitlab.jfronny.inceptum.model.curseforge.CurseforgeMod;
|
|||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.PathUtil;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.IWModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.source.CurseforgeModSource;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModDownload;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModSource;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModrinthModSource;
|
||||
import io.gitlab.jfronny.inceptum.model.modrinth.ModrinthSearchResult;
|
||||
import io.gitlab.jfronny.inceptum.model.modrinth.ModrinthVersion;
|
||||
import io.gitlab.jfronny.inceptum.util.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.util.api.CurseforgeApi;
|
||||
import io.gitlab.jfronny.inceptum.util.api.ModrinthApi;
|
||||
|
@ -95,7 +96,7 @@ public class AddModWindow extends Window {
|
|||
ImGui.text(mod.description);
|
||||
ImGui.tableNextColumn();
|
||||
boolean alreadyPresent = false;
|
||||
for (ModsDirScanner.IWModDescription mdsMod : mds.getMods()) {
|
||||
for (IWModDescription mdsMod : mds.getMods()) {
|
||||
alreadyPresent = mdsMod.mod().isPresent()
|
||||
&& mdsMod.mod().get().sources.keySet().stream()
|
||||
.anyMatch(s -> s instanceof ModrinthModSource ms
|
||||
|
@ -173,7 +174,7 @@ public class AddModWindow extends Window {
|
|||
ImGui.text(mod.summary);
|
||||
ImGui.tableNextColumn();
|
||||
boolean alreadyPresent = false;
|
||||
for (ModsDirScanner.IWModDescription mdsMod : mds.getMods()) {
|
||||
for (IWModDescription mdsMod : mds.getMods()) {
|
||||
alreadyPresent = mdsMod.mod().isPresent()
|
||||
&& mdsMod.mod().get().sources.keySet().stream()
|
||||
.anyMatch(s -> s instanceof CurseforgeModSource ms
|
||||
|
@ -224,7 +225,7 @@ public class AddModWindow extends Window {
|
|||
}
|
||||
|
||||
public static DownloadMeta download(ModSource ms, Path metaFile, ModsDirScanner mds) throws IOException {
|
||||
for (ModsDirScanner.IWModDescription value : mds.getMods()) {
|
||||
for (IWModDescription value : mds.getMods()) {
|
||||
if (value.mod().isEmpty()) continue;
|
||||
for (ModSource source : value.mod().get().sources.keySet()) {
|
||||
if (ms.equals(source)) {
|
||||
|
|
|
@ -11,6 +11,8 @@ import io.gitlab.jfronny.inceptum.frontend.gui.dialog.ProcessStateWatcherWindow;
|
|||
import io.gitlab.jfronny.inceptum.model.inceptum.Config;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.util.*;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.IWModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModSource;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.Status;
|
||||
|
@ -152,11 +154,11 @@ public class InstanceEditWindow extends Window {
|
|||
Utils.openFile(path.resolve("config").toFile());
|
||||
}
|
||||
try {
|
||||
Set<ModsDirScanner.IWModDescription> modSet = mds.getMods();
|
||||
Set<IWModDescription> modSet = mds.getMods();
|
||||
boolean updatesFound = false;
|
||||
float scannedPercentage = 0;
|
||||
boolean hasUnScanned = false;
|
||||
for (ModsDirScanner.IWModDescription mod : modSet) {
|
||||
for (IWModDescription mod : modSet) {
|
||||
if (mds.hasScanned(mod.path())) scannedPercentage++;
|
||||
else hasUnScanned = true;
|
||||
if (mod.mod().isEmpty()) continue;
|
||||
|
@ -176,7 +178,7 @@ public class InstanceEditWindow extends Window {
|
|||
else
|
||||
filterUpdates.set(false);
|
||||
ImGui.separator();
|
||||
for (ModsDirScanner.IWModDescription mod : modSet) {
|
||||
for (IWModDescription mod : modSet) {
|
||||
updatesFound = false;
|
||||
if (mod.mod().isPresent()) {
|
||||
for (Optional<ModSource> value : mod.mod().get().sources.values()) {
|
||||
|
@ -190,7 +192,7 @@ public class InstanceEditWindow extends Window {
|
|||
try {
|
||||
Files.move(mod.path(), newSel);
|
||||
if (mod.path().equals(selected)) selected = newSel;
|
||||
mod = new ModsDirScanner.IWModDescription(newSel, mod);
|
||||
mod = new IWModDescription(newSel, mod);
|
||||
} catch (IOException e) {
|
||||
Inceptum.showError("Could not change disabled state", e);
|
||||
}
|
||||
|
@ -207,7 +209,7 @@ public class InstanceEditWindow extends Window {
|
|||
if (selected == null) {
|
||||
ImGui.text("Select a mod to view settings");
|
||||
} else if (mds.hasScanned(selected)) {
|
||||
ModsDirScanner.IWModDescription md = mds.get(selected);
|
||||
IWModDescription md = mds.get(selected);
|
||||
ImGui.text(md.getName());
|
||||
ImGui.separator();
|
||||
for (String s : md.getDescription()) {
|
||||
|
@ -379,7 +381,7 @@ public class InstanceEditWindow extends Window {
|
|||
}
|
||||
}
|
||||
|
||||
private void delete(ModsDirScanner.IWModDescription md) throws IOException {
|
||||
private void delete(IWModDescription md) throws IOException {
|
||||
ModManager.delete(md, path.resolve("mods"), mds);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package io.gitlab.jfronny.inceptum.util;
|
|||
|
||||
import io.gitlab.jfronny.inceptum.model.curseforge.CurseforgeModpackManifest;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.IWModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
import io.gitlab.jfronny.inceptum.util.source.CurseforgeModSource;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModSource;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
|
@ -49,7 +51,7 @@ public class InstanceExporter {
|
|||
state.incrementStep("Adding mods");
|
||||
manifest.files = new LinkedHashSet<>();
|
||||
if (!mds.isComplete()) throw new IOException("Mods dir scan is not yet completed");
|
||||
modsLoop: for (ModsDirScanner.IWModDescription mod : mds.getMods()) {
|
||||
modsLoop: for (IWModDescription mod : mds.getMods()) {
|
||||
if (!PathUtil.isEnabled(mod.path())) return;
|
||||
state.updateStep(mod.path().toString());
|
||||
if (PathUtil.isImod(mod.path())) {
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.util.mds.IWModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ModManager {
|
||||
public static void delete(ModsDirScanner.IWModDescription md, Path modsDirectory, ModsDirScanner mds) throws IOException {
|
||||
public static void delete(IWModDescription md, Path modsDirectory, ModsDirScanner mds) throws IOException {
|
||||
Utils.deleteRecursive(md.path());
|
||||
if (md.imod().isPresent() && Files.exists(md.imod().get())) Files.delete(md.imod().get());
|
||||
if (md.mod().isPresent()) {
|
||||
for (String dependency : md.mod().get().dependencies) {
|
||||
Path dep = modsDirectory.resolve(dependency);
|
||||
ModsDirScanner.IWModDescription dmd = mds.get(dep);
|
||||
IWModDescription dmd = mds.get(dep);
|
||||
if (dmd.mod().isPresent()) {
|
||||
dmd.mod().get().dependencies.remove(md.path().getFileName().toString());
|
||||
if (dmd.mod().get().dependencies.isEmpty()) delete(dmd, modsDirectory, mds);
|
||||
|
|
|
@ -1,269 +0,0 @@
|
|||
package io.gitlab.jfronny.inceptum.util;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import io.gitlab.jfronny.inceptum.model.fabric.FabricModJson;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModSource;
|
||||
import io.gitlab.jfronny.inceptum.util.lambda.ThrowingConsumer;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class ModsDirScanner implements Closeable {
|
||||
private static final Map<Path, ModsDirScanner> scanners = new HashMap<>();
|
||||
private final Map<Path, IWModDescription> descriptions = new HashMap<>();
|
||||
private final Set<Path> scannedPaths = new HashSet<>();
|
||||
private final Thread th;
|
||||
private final Path modsDir;
|
||||
private final InstanceMeta instance;
|
||||
private boolean disposed = false;
|
||||
|
||||
private ModsDirScanner(Path modsDir, InstanceMeta instance) {
|
||||
this.modsDir = modsDir;
|
||||
this.instance = instance;
|
||||
this.th = new Thread(this::scanTaskInternal);
|
||||
}
|
||||
|
||||
public static ModsDirScanner get(Path modsDir, InstanceMeta instance) {
|
||||
if (scanners.containsKey(modsDir)) {
|
||||
ModsDirScanner mds = scanners.get(modsDir);
|
||||
if (mds.instance.equals(instance))
|
||||
return mds;
|
||||
mds.close();
|
||||
}
|
||||
ModsDirScanner mds = new ModsDirScanner(modsDir, instance);
|
||||
scanners.put(modsDir, mds);
|
||||
return mds;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
if (!Files.isDirectory(modsDir)) return true;
|
||||
try {
|
||||
for (Path path : Utils.ls(modsDir)) {
|
||||
if (!descriptions.containsKey(path)) return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!th.isAlive())
|
||||
th.start();
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return instance.getMinecraftVersion();
|
||||
}
|
||||
|
||||
public Set<IWModDescription> getMods() throws IOException {
|
||||
Set<IWModDescription> mods = new TreeSet<>();
|
||||
if (Files.isDirectory(modsDir)) {
|
||||
for (Path path : Utils.ls(modsDir)) {
|
||||
if (PathUtil.isImod(path) && Files.exists(PathUtil.trimImod(path)))
|
||||
continue;
|
||||
mods.add(get(path));
|
||||
}
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
public IWModDescription get(Path path) {
|
||||
if (!descriptions.containsKey(path)) {
|
||||
// not yet scanned
|
||||
descriptions.put(path, new IWModDescription(path));
|
||||
}
|
||||
return descriptions.get(path);
|
||||
}
|
||||
|
||||
public void invalidate(Path path) {
|
||||
descriptions.remove(path);
|
||||
scannedPaths.remove(path);
|
||||
}
|
||||
|
||||
public boolean hasScanned(Path path) {
|
||||
return scannedPaths.contains(path);
|
||||
}
|
||||
|
||||
private void scanTaskInternal() {
|
||||
while (!disposed) {
|
||||
runOnce(descriptions::put);
|
||||
}
|
||||
}
|
||||
|
||||
public void runOnce(BiConsumer<Path, IWModDescription> discovered) {
|
||||
try {
|
||||
if (!Files.isDirectory(modsDir)) {
|
||||
Thread.sleep(5000);
|
||||
return;
|
||||
}
|
||||
for (Path mods : Utils.ls(modsDir)) {
|
||||
if (disposed) return;
|
||||
scanFile(mods, discovered);
|
||||
}
|
||||
Thread.sleep(100);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Utils.LOGGER.error("Could not list mods", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void scanFile(Path file, BiConsumer<Path, IWModDescription> discovered) {
|
||||
if (!Files.exists(file)) return;
|
||||
if (Files.isDirectory(file)) {
|
||||
discovered.accept(file, new IWModDescription(file));
|
||||
scannedPaths.add(file);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (PathUtil.isJar(file)) {
|
||||
final var imod = new Object() {
|
||||
Path i = file.getParent().resolve(file.getFileName() + PathUtil.EXT_IMOD);
|
||||
ModDescription md;
|
||||
};
|
||||
// mod description
|
||||
if (!Files.exists(imod.i)) {
|
||||
Utils.writeObject(imod.i, ModDescription.of(file));
|
||||
}
|
||||
imod.md = Utils.loadObject(imod.i, ModDescription.class);
|
||||
evaluateSources(imod.md, file, imod.i, newImod -> {
|
||||
imod.i = newImod;
|
||||
imod.md = Utils.loadObject(imod.i, ModDescription.class);
|
||||
});
|
||||
discovered.accept(imod.i, new IWModDescription(file, Optional.of(imod.md), getFmj(file, imod.md), Optional.of(imod.i)));
|
||||
scannedPaths.add(file);
|
||||
return;
|
||||
}
|
||||
if (PathUtil.isImod(file)) {
|
||||
String fn = file.getFileName().toString();
|
||||
Path modFile = file.getParent().resolve(fn.substring(0, fn.length() - 5));
|
||||
final var imod = new Object() {
|
||||
Path i = file;
|
||||
ModDescription md = Utils.loadObject(file, ModDescription.class);
|
||||
};
|
||||
evaluateSources(imod.md, modFile, imod.i, newImod -> {
|
||||
imod.i = newImod;
|
||||
imod.md = Utils.loadObject(imod.i, ModDescription.class);
|
||||
});
|
||||
discovered.accept(imod.i, new IWModDescription(imod.i, Optional.of(imod.md), getFmj(modFile, imod.md), Optional.of(imod.i)));
|
||||
scannedPaths.add(file);
|
||||
return;
|
||||
}
|
||||
discovered.accept(file, new IWModDescription(file));
|
||||
scannedPaths.add(file);
|
||||
} catch (IOException | URISyntaxException | JsonParseException e) {
|
||||
Utils.LOGGER.error("Could not scan file for mod info", e);
|
||||
}
|
||||
}
|
||||
|
||||
private <TEx extends Throwable> void evaluateSources(ModDescription md, Path modFile, Path imodFile, ThrowingConsumer<Path, TEx> imodModified) throws IOException, TEx {
|
||||
boolean modified = false;
|
||||
if (md.initialize(getGameVersion())) {
|
||||
Utils.writeObject(imodFile, md);
|
||||
modified = true;
|
||||
}
|
||||
ModSource selectedSource = null;
|
||||
for (ModSource source : md.sources.keySet()) {
|
||||
source.getUpdate(getGameVersion());
|
||||
if (!Files.exists(source.getJarPath())) source.download();
|
||||
selectedSource = source;
|
||||
}
|
||||
if (selectedSource != null && Files.exists(modFile)) {
|
||||
Files.delete(modFile);
|
||||
Path newImod = imodFile.getParent().resolve(selectedSource.getShortName() + PathUtil.EXT_IMOD);
|
||||
Files.move(imodFile, newImod);
|
||||
imodFile = newImod;
|
||||
modified = true;
|
||||
}
|
||||
if (modified) imodModified.consume(imodFile);
|
||||
}
|
||||
|
||||
private Optional<FabricModJson> getFmj(Path modJarDefault, ModDescription md) throws IOException, URISyntaxException {
|
||||
if (!Files.exists(modJarDefault)) {
|
||||
if (md.sources.isEmpty()) {
|
||||
throw new FileNotFoundException("Mod " + modJarDefault.getFileName().toString() + " doesn't specify a source and has no file");
|
||||
}
|
||||
modJarDefault = List.copyOf(md.sources.keySet()).get(0).getJarPath();
|
||||
}
|
||||
try (FileSystem fs = Utils.openZipFile(modJarDefault, false)) {
|
||||
return Files.exists(fs.getPath("fabric.mod.json"))
|
||||
? Optional.of(Utils.loadObject(fs.getPath("fabric.mod.json"), FabricModJson.class))
|
||||
: Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeAll() {
|
||||
for (ModsDirScanner value : scanners.values().toArray(ModsDirScanner[]::new)) {
|
||||
value.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
disposed = true;
|
||||
scanners.remove(modsDir);
|
||||
}
|
||||
|
||||
public record IWModDescription(Path path, Optional<ModDescription> mod, Optional<FabricModJson> fmj, Optional<Path> imod) implements Comparable<IWModDescription> {
|
||||
public String getName() {
|
||||
if (fmj.isEmpty()) return path.getFileName().toString();
|
||||
String base;
|
||||
if (fmj.get().name != null) base = fmj.get().name;
|
||||
else if (fmj.get().id != null) base = fmj.get().id;
|
||||
else {
|
||||
base = path.getFileName().toString();
|
||||
if (Files.isDirectory(path))
|
||||
base += '/';
|
||||
}
|
||||
if (fmj.get().version != null) base += ' ' + fmj.get().version;
|
||||
return base;
|
||||
}
|
||||
|
||||
public String[] getDescription() {
|
||||
try {
|
||||
if (fmj.isPresent() && fmj.get().description != null) {
|
||||
return fmj.get().description.split("\n");
|
||||
}
|
||||
else if (Files.isDirectory(path)) {
|
||||
return Utils.lsVi(path);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not get description", e);
|
||||
}
|
||||
return new String[] {"No description is available", "This mod may have been added manually"};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a description with only the path and no additional information
|
||||
* @param path The path of the mod entry
|
||||
*/
|
||||
public IWModDescription(Path path) {
|
||||
this(path,
|
||||
Files.isDirectory(path) ? Optional.empty() : Optional.of(ModDescription.of(path)),
|
||||
Optional.empty(),
|
||||
Files.exists(PathUtil.appendImod(path)) ? Optional.of(PathUtil.appendImod(path)) : Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new mod description based on an existing one with an adjusted path
|
||||
* @param path The new path to use
|
||||
* @param base The mod description to copy
|
||||
*/
|
||||
public IWModDescription(Path path, IWModDescription base) {
|
||||
this(path, base.mod, base.fmj, base.imod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IWModDescription o) {
|
||||
return getName().compareTo(o.getName());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
package io.gitlab.jfronny.inceptum.util.install;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.Inceptum;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.LoaderInfo;
|
||||
import io.gitlab.jfronny.inceptum.model.mojang.VersionInfo;
|
||||
import io.gitlab.jfronny.inceptum.model.mojang.VersionsListInfo;
|
||||
import io.gitlab.jfronny.inceptum.util.InstanceLock;
|
||||
import io.gitlab.jfronny.inceptum.util.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.util.ProcessState;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.util.api.FabricMetaApi;
|
||||
|
@ -27,23 +25,14 @@ public class Steps {
|
|||
new DownloadClientStep(),
|
||||
new DownloadLibrariesStep(),
|
||||
new DownloadAssetsStep(),
|
||||
new WriteMetadataStep())
|
||||
);
|
||||
new WriteMetadataStep(),
|
||||
new RunMdsStep()
|
||||
));
|
||||
|
||||
public static ProcessState createProcessState() {
|
||||
return new ProcessState(STEPS.size(), "Initializing");
|
||||
}
|
||||
|
||||
public static void reDownload(ProcessState state) throws IOException {
|
||||
Utils.ls(MetaHolder.INSTANCE_DIR, p -> {
|
||||
try {
|
||||
reDownload(p, state);
|
||||
} catch (IOException e) {
|
||||
Inceptum.showError("Could not execute refresh task", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void reDownload(Path instance, ProcessState state) throws IOException {
|
||||
if (InstanceLock.isLocked(instance)) return;
|
||||
InstanceMeta im = Utils.loadObject(instance.resolve("instance.json"), InstanceMeta.class);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package io.gitlab.jfronny.inceptum.util.install.steps;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.util.MetaHolder;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.util.install.SetupStepInfo;
|
||||
import io.gitlab.jfronny.inceptum.util.install.Step;
|
||||
import io.gitlab.jfronny.inceptum.util.mds.ModsDirScanner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class RunMdsStep implements Step {
|
||||
@Override
|
||||
public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException {
|
||||
info.setState("Running MDS");
|
||||
Path instance = MetaHolder.INSTANCE_DIR.resolve(info.name());
|
||||
ModsDirScanner.get(instance.resolve("mods"), Utils.loadObject(instance.resolve("instance.json"), InstanceMeta.class))
|
||||
.runOnce((path, iwModDescription) -> {
|
||||
info.setState("Scanned " + path);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package io.gitlab.jfronny.inceptum.util.mds;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import io.gitlab.jfronny.inceptum.model.fabric.FabricModJson;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.PathUtil;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
import io.gitlab.jfronny.inceptum.util.lambda.ThrowingConsumer;
|
||||
import io.gitlab.jfronny.inceptum.util.source.ModSource;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public record FileScanTask(Path file, BiConsumer<Path, IWModDescription> discovered, String gameVersion) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!Files.exists(file)) return;
|
||||
if (Files.isDirectory(file)) {
|
||||
discovered.accept(file, new IWModDescription(file));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (PathUtil.isJar(file)) {
|
||||
final var imod = new Object() {
|
||||
Path i = file.getParent().resolve(file.getFileName() + PathUtil.EXT_IMOD);
|
||||
ModDescription md;
|
||||
};
|
||||
// mod description
|
||||
if (!Files.exists(imod.i)) {
|
||||
Utils.writeObject(imod.i, ModDescription.of(file));
|
||||
}
|
||||
imod.md = Utils.loadObject(imod.i, ModDescription.class);
|
||||
evaluateSources(imod.md, file, imod.i, newImod -> {
|
||||
imod.i = newImod;
|
||||
imod.md = Utils.loadObject(imod.i, ModDescription.class);
|
||||
});
|
||||
discovered.accept(imod.i, new IWModDescription(file, Optional.of(imod.md), getFmj(file, imod.md), Optional.of(imod.i)));
|
||||
return;
|
||||
}
|
||||
if (PathUtil.isImod(file)) {
|
||||
String fn = file.getFileName().toString();
|
||||
Path modFile = file.getParent().resolve(fn.substring(0, fn.length() - 5));
|
||||
final var imod = new Object() {
|
||||
Path i = file;
|
||||
ModDescription md = Utils.loadObject(file, ModDescription.class);
|
||||
};
|
||||
evaluateSources(imod.md, modFile, imod.i, newImod -> {
|
||||
imod.i = newImod;
|
||||
imod.md = Utils.loadObject(imod.i, ModDescription.class);
|
||||
});
|
||||
discovered.accept(imod.i, new IWModDescription(imod.i, Optional.of(imod.md), getFmj(modFile, imod.md), Optional.of(imod.i)));
|
||||
return;
|
||||
}
|
||||
discovered.accept(file, new IWModDescription(file));
|
||||
} catch (IOException | URISyntaxException | JsonParseException e) {
|
||||
Utils.LOGGER.error("Could not scan file for mod info", e);
|
||||
}
|
||||
}
|
||||
|
||||
private <TEx extends Throwable> void evaluateSources(ModDescription md, Path modFile, Path imodFile, ThrowingConsumer<Path, TEx> imodModified) throws IOException, TEx {
|
||||
boolean modified = false;
|
||||
if (md.initialize(gameVersion)) {
|
||||
Utils.writeObject(imodFile, md);
|
||||
modified = true;
|
||||
}
|
||||
ModSource selectedSource = null;
|
||||
for (ModSource source : md.sources.keySet()) {
|
||||
source.getUpdate(gameVersion);
|
||||
if (!Files.exists(source.getJarPath())) source.download();
|
||||
selectedSource = source;
|
||||
}
|
||||
if (selectedSource != null && Files.exists(modFile)) {
|
||||
Files.delete(modFile);
|
||||
Path newImod = imodFile.getParent().resolve(selectedSource.getShortName() + PathUtil.EXT_IMOD);
|
||||
Files.move(imodFile, newImod);
|
||||
imodFile = newImod;
|
||||
modified = true;
|
||||
}
|
||||
if (modified) imodModified.consume(imodFile);
|
||||
}
|
||||
|
||||
private Optional<FabricModJson> getFmj(Path modJarDefault, ModDescription md) throws IOException, URISyntaxException {
|
||||
if (!Files.exists(modJarDefault)) {
|
||||
if (md.sources.isEmpty()) {
|
||||
throw new FileNotFoundException("Mod " + modJarDefault.getFileName().toString() + " doesn't specify a source and has no file");
|
||||
}
|
||||
modJarDefault = List.copyOf(md.sources.keySet()).get(0).getJarPath();
|
||||
}
|
||||
try (FileSystem fs = Utils.openZipFile(modJarDefault, false)) {
|
||||
return Files.exists(fs.getPath("fabric.mod.json"))
|
||||
? Optional.of(Utils.loadObject(fs.getPath("fabric.mod.json"), FabricModJson.class))
|
||||
: Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package io.gitlab.jfronny.inceptum.util.mds;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.model.fabric.FabricModJson;
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
|
||||
import io.gitlab.jfronny.inceptum.util.PathUtil;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public record IWModDescription(Path path, Optional<ModDescription> mod, Optional<FabricModJson> fmj, Optional<Path> imod) implements Comparable<IWModDescription> {
|
||||
public String getName() {
|
||||
if (fmj.isEmpty()) return path.getFileName().toString();
|
||||
String base;
|
||||
if (fmj.get().name != null) base = fmj.get().name;
|
||||
else if (fmj.get().id != null) base = fmj.get().id;
|
||||
else {
|
||||
base = path.getFileName().toString();
|
||||
if (Files.isDirectory(path))
|
||||
base += '/';
|
||||
}
|
||||
if (fmj.get().version != null) base += ' ' + fmj.get().version;
|
||||
return base;
|
||||
}
|
||||
|
||||
public String[] getDescription() {
|
||||
try {
|
||||
if (fmj.isPresent() && fmj.get().description != null) {
|
||||
return fmj.get().description.split("\n");
|
||||
} else if (Files.isDirectory(path)) {
|
||||
return Utils.lsVi(path);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Utils.LOGGER.error("Could not get description", e);
|
||||
}
|
||||
return new String[]{"No description is available", "This mod may have been added manually"};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a description with only the path and no additional information
|
||||
*
|
||||
* @param path The path of the mod entry
|
||||
*/
|
||||
public IWModDescription(Path path) {
|
||||
this(path,
|
||||
Files.isDirectory(path) ? Optional.empty() : Optional.of(ModDescription.of(path)),
|
||||
Optional.empty(),
|
||||
Files.exists(PathUtil.appendImod(path)) ? Optional.of(PathUtil.appendImod(path)) : Optional.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new mod description based on an existing one with an adjusted path
|
||||
*
|
||||
* @param path The new path to use
|
||||
* @param base The mod description to copy
|
||||
*/
|
||||
public IWModDescription(Path path, IWModDescription base) {
|
||||
this(path, base.mod, base.fmj, base.imod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IWModDescription o) {
|
||||
return getName().compareTo(o.getName());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package io.gitlab.jfronny.inceptum.util.mds;
|
||||
|
||||
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
|
||||
import io.gitlab.jfronny.inceptum.util.PathUtil;
|
||||
import io.gitlab.jfronny.inceptum.util.Utils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class ModsDirScanner implements Closeable {
|
||||
private static final Map<Path, ModsDirScanner> SCANNERS = new HashMap<>();
|
||||
private static final ExecutorService POOL = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
|
||||
private final Map<Path, IWModDescription> descriptions = new HashMap<>();
|
||||
private final Set<Path> scannedPaths = new HashSet<>();
|
||||
private final Thread th;
|
||||
private final Path modsDir;
|
||||
private final InstanceMeta instance;
|
||||
private boolean disposed = false;
|
||||
|
||||
private ModsDirScanner(Path modsDir, InstanceMeta instance) {
|
||||
this.modsDir = modsDir;
|
||||
this.instance = instance;
|
||||
this.th = new Thread(this::scanTaskInternal);
|
||||
}
|
||||
|
||||
public static ModsDirScanner get(Path modsDir, InstanceMeta instance) {
|
||||
if (SCANNERS.containsKey(modsDir)) {
|
||||
ModsDirScanner mds = SCANNERS.get(modsDir);
|
||||
if (mds.instance.equals(instance))
|
||||
return mds;
|
||||
mds.close();
|
||||
}
|
||||
ModsDirScanner mds = new ModsDirScanner(modsDir, instance);
|
||||
SCANNERS.put(modsDir, mds);
|
||||
return mds;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
if (!Files.isDirectory(modsDir)) return true;
|
||||
try {
|
||||
for (Path path : Utils.ls(modsDir)) {
|
||||
if (!descriptions.containsKey(path)) return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!th.isAlive())
|
||||
th.start();
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return instance.getMinecraftVersion();
|
||||
}
|
||||
|
||||
public Set<IWModDescription> getMods() throws IOException {
|
||||
Set<IWModDescription> mods = new TreeSet<>();
|
||||
if (Files.isDirectory(modsDir)) {
|
||||
for (Path path : Utils.ls(modsDir)) {
|
||||
if (PathUtil.isImod(path) && Files.exists(PathUtil.trimImod(path)))
|
||||
continue;
|
||||
mods.add(get(path));
|
||||
}
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
public IWModDescription get(Path path) {
|
||||
if (!descriptions.containsKey(path)) {
|
||||
// not yet scanned
|
||||
descriptions.put(path, new IWModDescription(path));
|
||||
}
|
||||
return descriptions.get(path);
|
||||
}
|
||||
|
||||
public void invalidate(Path path) {
|
||||
descriptions.remove(path);
|
||||
scannedPaths.remove(path);
|
||||
}
|
||||
|
||||
public boolean hasScanned(Path path) {
|
||||
return scannedPaths.contains(path);
|
||||
}
|
||||
|
||||
private void scanTaskInternal() {
|
||||
while (!disposed) {
|
||||
runOnce(descriptions::put);
|
||||
}
|
||||
}
|
||||
|
||||
public void runOnce(BiConsumer<Path, IWModDescription> discovered) {
|
||||
try {
|
||||
if (!Files.isDirectory(modsDir)) {
|
||||
Thread.sleep(5000);
|
||||
return;
|
||||
}
|
||||
Set<Future<?>> tasks = new HashSet<>();
|
||||
for (Path mods : Utils.ls(modsDir)) {
|
||||
if (disposed) return;
|
||||
tasks.add(POOL.submit(new FileScanTask(mods, discovered.andThen((path, iwModDescription) -> scannedPaths.add(path)), getGameVersion())));
|
||||
}
|
||||
for (Future<?> task : tasks) {
|
||||
try {
|
||||
task.get();
|
||||
} catch (ExecutionException e) {
|
||||
Utils.LOGGER.error("Failed to execute ScanTask", e);
|
||||
}
|
||||
}
|
||||
Thread.sleep(100);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Utils.LOGGER.error("Could not list mods", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void closeAll() {
|
||||
for (ModsDirScanner value : SCANNERS.values().toArray(ModsDirScanner[]::new)) {
|
||||
value.close();
|
||||
}
|
||||
POOL.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
disposed = true;
|
||||
SCANNERS.remove(modsDir);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue