package io.gitlab.jfronny.inceptum.launcher.system.instance; import gsoncompile.extensions.io.gitlab.jfronny.inceptum.launcher.model.inceptum.InstanceMeta.GC_InstanceMeta; 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.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 { private static final String INCEPTUM_LOCK = "inceptum.lock"; private static final String INCEPTUM_SETUP_LOCK = "inceptum.setup.lock"; public Instance(Path path, InstanceMeta meta) throws IOException { this(generateId(path.fileName.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.fileName.toString().compareTo(entry.path.fileName.toString()); return -1; } if (time2 == 0) return 1; return Long.compare(time1, time2); } @Override public String toString() { return path.fileName.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.fileName.toString(); } public boolean isFabric() { return GameVersionParser.isFabric(meta.version); } public String getGameVersion() { return GameVersionParser.getGameVersion(meta.version); } public String getLoaderVersion() { return GameVersionParser.getLoaderVersion(meta.version); } public boolean isSetupLocked() { return Files.exists(path.resolve(INCEPTUM_SETUP_LOCK)); } 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(INCEPTUM_SETUP_LOCK))) { if (!state) Files.delete(instanceDir.resolve(INCEPTUM_SETUP_LOCK)); } else { if (state) Files.createDirectories(instanceDir.resolve(INCEPTUM_SETUP_LOCK)); } } public boolean isRunningLocked() { if (!Files.exists(path.resolve(INCEPTUM_LOCK))) return false; try { if (ProcessUtils.isProcessAlive(Files.readString(path.resolve(INCEPTUM_LOCK)))) return true; Files.delete(path.resolve(INCEPTUM_LOCK)); } catch (IOException e) { Utils.LOGGER.error("Could not read running lock of " + name, e); } return false; } public void setRunningLock(long pid) throws IOException { Files.writeString(path.resolve(INCEPTUM_LOCK), Long.toString(pid)); } public boolean isLocked() { return isSetupLocked() || isRunningLocked(); } public void writeMeta() { try { GC_InstanceMeta.write(meta, path.resolve("instance.json")); } catch (IOException e) { Utils.LOGGER.error("Could not write instance config", e); } } }