170 lines
5.9 KiB
Java
170 lines
5.9 KiB
Java
package io.gitlab.jfronny.inceptum.launcher.system.mds;
|
|
|
|
import io.gitlab.jfronny.commons.io.JFiles;
|
|
import io.gitlab.jfronny.commons.ref.R;
|
|
import io.gitlab.jfronny.inceptum.common.Utils;
|
|
import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta;
|
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Mod;
|
|
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.util.GameVersionParser;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.*;
|
|
import java.util.*;
|
|
import java.util.concurrent.*;
|
|
import java.util.function.BiConsumer;
|
|
|
|
import static java.nio.file.StandardWatchEventKinds.*;
|
|
|
|
class ModsDirScannerImpl implements ModsDirScanner {
|
|
private static final Map<Path, ModsDirScannerImpl> SCANNERS = new HashMap<>();
|
|
private static final ExecutorService POOL = Executors.newFixedThreadPool(Runtime.runtime.availableProcessors(), new NamedThreadFactory("mds"));
|
|
private final Map<Path, Mod> descriptions = new HashMap<>();
|
|
private final Set<Path> scannedPaths = new HashSet<>();
|
|
private final Thread th;
|
|
private final ProtoInstance instance;
|
|
private final WatchService service;
|
|
private boolean disposed = false;
|
|
|
|
private ModsDirScannerImpl(Path modsDir, InstanceMeta instance) throws IOException {
|
|
this.instance = new ProtoInstance(modsDir, this, instance);
|
|
this.th = new Thread(this::scanTaskInternal, "mds-" + modsDir.parent.fileName);
|
|
this.service = FileSystems.getDefault().newWatchService();
|
|
modsDir.register(service, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);
|
|
}
|
|
|
|
protected static ModsDirScannerImpl get(Path modsDir, InstanceMeta instance) throws IOException {
|
|
if (SCANNERS.containsKey(modsDir)) {
|
|
ModsDirScannerImpl mds = SCANNERS[modsDir];
|
|
if (mds.instance.meta.equals(instance)) return mds;
|
|
mds.close();
|
|
}
|
|
ModsDirScannerImpl mds = new ModsDirScannerImpl(modsDir, instance);
|
|
SCANNERS[modsDir] = mds;
|
|
return mds;
|
|
}
|
|
|
|
@Override
|
|
public boolean isComplete() {
|
|
if (!Files.isDirectory(instance.modsDir)) return true;
|
|
try {
|
|
for (Path path : JFiles.list(instance.modsDir)) {
|
|
if (!descriptions.containsKey(path)) 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 String getGameVersion() {
|
|
return GameVersionParser.getGameVersion(instance.meta.version);
|
|
}
|
|
|
|
@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[path];
|
|
}
|
|
|
|
@Override
|
|
public void invalidate(Path path) {
|
|
descriptions.remove(path);
|
|
scannedPaths.remove(path);
|
|
}
|
|
|
|
public boolean hasScanned(Path path) {
|
|
return scannedPaths.contains(path)
|
|
|| scannedPaths.contains(path.parent.resolve(path.fileName.toString() + ".imod"));
|
|
}
|
|
|
|
private void scanTaskInternal() {
|
|
while (!disposed) {
|
|
runOnce(R::nop);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void runOnce(BiConsumer<Path, Mod> discovered) {
|
|
try {
|
|
if (!Files.isDirectory(instance.modsDir)) {
|
|
return;
|
|
}
|
|
Set<Future<?>> tasks = new HashSet<>();
|
|
discovered = discovered.andThen((path, iwModDescription) -> {
|
|
scannedPaths.add(path);
|
|
descriptions[path] = iwModDescription;
|
|
});
|
|
Set<Path> toScan;
|
|
if (descriptions.isEmpty) {
|
|
toScan = Set.copyOf(JFiles.list(instance.modsDir));
|
|
} else {
|
|
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.warn("Could not reset config watch key");
|
|
}
|
|
JFiles.listTo(instance.modsDir, path -> {
|
|
if (!descriptions.containsKey(path)) toScan.add(path);
|
|
});
|
|
}
|
|
for (Path p : toScan) {
|
|
tasks.add(POOL.submit(new FileScanTask(instance, p, discovered, gameVersion)));
|
|
}
|
|
for (Future<?> task : tasks) {
|
|
try {
|
|
task.get();
|
|
} catch (ExecutionException e) {
|
|
Utils.LOGGER.error("Failed to execute ScanTask", e);
|
|
}
|
|
}
|
|
} catch (IOException | InterruptedException e) {
|
|
Utils.LOGGER.error("Could not list mods", e);
|
|
}
|
|
}
|
|
|
|
public static void closeAll() {
|
|
for (ModsDirScannerImpl value : SCANNERS.values().toArray(ModsDirScannerImpl[]::new)) {
|
|
try {
|
|
value.close();
|
|
} catch (IOException e) {
|
|
Utils.LOGGER.error("Could not close MDS", e);
|
|
}
|
|
}
|
|
POOL.shutdown();
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
disposed = true;
|
|
service.close();
|
|
SCANNERS.remove(instance.modsDir);
|
|
}
|
|
}
|