package io.gitlab.jfronny.inceptum.launcher.system.instance; import io.gitlab.jfronny.commons.ref.R; import io.gitlab.jfronny.inceptum.common.Utils; import io.gitlab.jfronny.inceptum.launcher.model.inceptum.GC_InstanceMeta; import io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta; import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner; import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser; import io.gitlab.jfronny.inceptum.launcher.util.ProcessUtils; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Set; public record Instance(String id, Path path, InstanceMeta meta, ModsDirScanner mds) implements Comparable { public static final String LOCK_NAME = "inceptum.lock"; public static final String SETUP_LOCK_NAME = "inceptum.setup.lock"; public static final String CONFIG_NAME = "instance.json"; public Instance(Path path, InstanceMeta meta) throws IOException { this(generateId(path.getFileName().toString()), path, meta, ModsDirScanner.get(path.resolve("mods"), meta)); } public Instance(String id, Path path, InstanceMeta meta, ModsDirScanner mds) { this.id = id; this.path = path.toAbsolutePath().normalize(); this.meta = meta; this.mds = mds; } /** * Converts any string into a set of lowercase ascii chars * * @param input string to convert * @return a string matching [a-p]* */ private static String generateId(String input) { StringBuilder result = new StringBuilder(); for (byte b : input.getBytes(StandardCharsets.UTF_8)) { int by = Byte.toUnsignedInt(b); int ch2 = by & 15; // right bits int ch1 = (by - ch2) / 16; // left bits result.append((char) ('a' + ch1)); result.append((char) ('a' + ch2)); } return result.toString(); } @Override public int compareTo(@NotNull Instance entry) { long time1 = meta.lastLaunched == null ? 0 : meta.lastLaunched; long time2 = entry.meta.lastLaunched == null ? 0 : entry.meta.lastLaunched; if (time1 == 0) { if (time2 == 0) return path.getFileName().toString().compareTo(entry.path.getFileName().toString()); return -1; } if (time2 == 0) return 1; return Long.compare(time1, time2); } @Override public String toString() { return path.getFileName().toString(); } public Path getModsDir() { return path.resolve("mods"); } public Path getConfigDir() { return path.resolve("config"); } public Set getMods() throws IOException { mds.runOnce(R::nop); return mds.getMods(); } public String getName() { return path.getFileName().toString(); } public boolean isFabric() { return GameVersionParser.isFabric(meta.gameVersion); } public String getGameVersion() { return GameVersionParser.getGameVersion(meta.gameVersion); } public String getLoaderVersion() { return GameVersionParser.getLoaderVersion(meta.gameVersion); } public boolean isSetupLocked() { return Files.exists(path.resolve(SETUP_LOCK_NAME)); } public void setSetupLock(boolean state) throws IOException { setSetupLock(path, state); } public static void setSetupLock(Path instanceDir, boolean state) throws IOException { if (Files.exists(instanceDir.resolve(SETUP_LOCK_NAME))) { if (!state) Files.deleteIfExists(instanceDir.resolve(SETUP_LOCK_NAME)); } else { if (state) Files.createDirectories(instanceDir.resolve(SETUP_LOCK_NAME)); } } public boolean isRunningLocked() { if (!Files.exists(path.resolve(LOCK_NAME))) return false; try { if (ProcessUtils.isProcessAlive(Files.readString(path.resolve(LOCK_NAME)))) return true; Files.deleteIfExists(path.resolve(LOCK_NAME)); } catch (IOException e) { Utils.LOGGER.error("Could not read running lock of " + getName(), e); } return false; } public boolean kill() { if (!isRunningLocked()) { Utils.LOGGER.info("Already killed"); return false; } try { if (!ProcessUtils.kill(Files.readString(path.resolve(LOCK_NAME)))) { Utils.LOGGER.error("Could not kill instance"); return false; } if (isRunningLocked()) { Utils.LOGGER.error("Still running after kill"); return false; } return true; } catch (IOException e) { Utils.LOGGER.error("Could not read running lock of " + getName(), e); } return false; } public void setRunningLock(long pid) throws IOException { Files.writeString(path.resolve(LOCK_NAME), Long.toString(pid)); } public boolean isLocked() { return isSetupLocked() || isRunningLocked(); } public void writeMeta() { try { GC_InstanceMeta.write(meta, path.resolve(CONFIG_NAME)); } catch (IOException e) { Utils.LOGGER.error("Could not write instance config", e); } } }