Inceptum/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/mds/ModsDirScannerImpl.java

171 lines
6.0 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);
}
}