Sevaral TODOs

This commit is contained in:
JFronny 2021-11-04 14:07:34 +01:00
parent e6e6c5822f
commit 56a16f5ee1
No known key found for this signature in database
GPG Key ID: BEC5ACBBD4EE17E5
12 changed files with 307 additions and 118 deletions

View File

@ -20,11 +20,11 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
//TODO mods browser
//TODO allow instance sync through metadata
public class Inceptum {
public static final Path CACHE_DIR = MetaHolder.BASE_PATH.resolve("cache");
@ -42,6 +42,7 @@ public class Inceptum {
.registerTypeAdapter(OauthTokenResponse.class, new OauthTokenResponseDeserializer())
.registerTypeAdapter(ComparableVersion.class, new ComparableVersionAdapter())
.registerTypeAdapter(ModSource.class, new ModSourceTypeAdapter())
.registerTypeAdapter(ModSourceMapDeserializer.modSourceMapType, new ModSourceMapDeserializer())
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.addSerializationExclusionStrategy(new GsonIgnoreExclusionStrategy())

View File

@ -0,0 +1,32 @@
package io.gitlab.jfronny.inceptum.gson;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
public class ModSourceMapDeserializer implements JsonSerializer<Map<ModSource, Optional<ModSource>>>, JsonDeserializer<Map<ModSource, Optional<ModSource>>> {
public static final Type modSourceMapType = new TypeToken<Map<ModSource, Optional<ModSource>>>(){}.getType();
@Override
public Map<ModSource, Optional<ModSource>> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (!json.isJsonArray()) throw new JsonParseException("Not an array");
Map<ModSource, Optional<ModSource>> res = new LinkedHashMap<>();
for (JsonElement element : json.getAsJsonArray()) {
res.put(context.deserialize(element, ModSource.class), Optional.empty());
}
return res;
}
@Override
public JsonElement serialize(Map<ModSource, Optional<ModSource>> src, Type typeOfSrc, JsonSerializationContext context) {
JsonArray res = new JsonArray();
for (ModSource source : src.keySet()) {
res.add(context.serialize(source, ModSource.class));
}
return res;
}
}

View File

@ -10,8 +10,8 @@ public class InstanceMeta {
public String java;
public Long minMem;
public Long maxMem;
public List<String> jvmArgsCustom; //TODO allow configuring
public List<String> gameArgsCustom; //TODO allow configuring
public List<String> jvmArgsCustom; //TODO allow configuring in UI
public List<String> gameArgsCustom; //TODO allow configuring in UI
public boolean isFabric() {
return version.startsWith(floaderPrefix);

View File

@ -1,13 +1,81 @@
package io.gitlab.jfronny.inceptum.model.inceptum;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.curseforge.CurseforgeFingerprint;
import io.gitlab.jfronny.inceptum.model.inceptum.source.CurseforgeModSource;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModrinthModSource;
import io.gitlab.jfronny.inceptum.util.HashUtils;
import io.gitlab.jfronny.inceptum.util.api.CurseforgeApi;
import io.gitlab.jfronny.inceptum.util.api.ModrinthApi;
import java.util.List;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class ModDescription {
public List<ModSource> sources;
public Map<ModSource, Optional<ModSource>> sources; //key: source, value: update
public String sha1;
public String murmur2;
public List<String> dependents; // by file name
public List<String> dependencies; // by file name
public void hydrateUpdates(String gameVersion) {
for (ModSource source : sources.keySet().toArray(ModSource[]::new)) {
addSource(source, gameVersion);
}
}
public static ModDescription of(Path mod, String gameVersion) {
String sha1 = null;
String murmur2 = null;
try {
byte[] data = Files.readAllBytes(mod);
sha1 = HashUtils.sha1(data);
murmur2 = HashUtils.murmur2(data);
} catch (IOException e) {
Inceptum.LOGGER.error("Could not read file hash", e);
}
return of(sha1, murmur2, Optional.empty(), gameVersion);
}
public static ModDescription of(String sha1, String murmur2, Optional<ModSource> knownSource, String gameVersion) {
ModDescription res = new ModDescription();
res.sources = new LinkedHashMap<>();
res.sha1 = sha1;
res.murmur2 = murmur2;
res.dependents = new ArrayList<>();
res.dependencies = new ArrayList<>();
knownSource.ifPresent(modSource -> res.addSource(modSource, gameVersion));
if (knownSource.isEmpty() || !(knownSource.get() instanceof ModrinthModSource)) {
try {
res.addSource(new ModrinthModSource(ModrinthApi.getVersionByHash(sha1).id), gameVersion);
}
catch (IOException e) {
// not found
}
}
if (knownSource.isEmpty() || !(knownSource.get() instanceof CurseforgeModSource)) {
try {
CurseforgeFingerprint cf = CurseforgeApi.checkFingerprint(murmur2);
if (!cf.exactMatches.isEmpty()) {
CurseforgeFingerprint.Mod f = cf.exactMatches.get(0);
res.addSource(new CurseforgeModSource(f.id, f.file.id), gameVersion);
}
}
catch (IOException e) {
// not found
}
}
return res;
}
public void addSource(ModSource source, String gameVersion) {
try {
sources.put(source, source.getUpdate(gameVersion));
} catch (IOException e) {
Inceptum.LOGGER.error("Could not check " + source.getName() + " for updates", e);
}
}
}

View File

@ -18,18 +18,19 @@ public final class CurseforgeModSource implements ModSource {
private final int projectId;
private final int fileId;
private final CurseforgeFile current;
private final CurseforgeMod mod;
public CurseforgeModSource(int projectId, int fileId) throws IOException {
this.projectId = projectId;
this.fileId = fileId;
current = CurseforgeApi.getFile(projectId, fileId);
this.current = CurseforgeApi.getFile(projectId, fileId);
this.mod = CurseforgeApi.getMod(projectId);
}
@Override
public ModDownload download(Path modsDir) throws IOException {
//TODO this doesn't work
Path path = modsDir.resolve(current.fileName);
Utils.downloadFile(current.downloadUrl.replace("edge.forgecdn.net", "media.forgecdn.net"), path);
Utils.downloadFile(current.downloadUrl, path);
byte[] data = Files.readAllBytes(path);
return new ModDownload(HashUtils.sha1(data), HashUtils.murmur2(data), path);
}
@ -47,7 +48,7 @@ public final class CurseforgeModSource implements ModSource {
@Override
public Optional<ModSource> getUpdate(String gameVersion) throws IOException {
//TODO test
for (CurseforgeMod.GameVersionLatestFile file : CurseforgeApi.getMod(projectId).gameVersionLatestFiles) {
for (CurseforgeMod.GameVersionLatestFile file : mod.gameVersionLatestFiles) {
if (file.gameVersion.equals(gameVersion)) {
return file.projectFileId == fileId
? Optional.empty()
@ -62,6 +63,11 @@ public final class CurseforgeModSource implements ModSource {
return current.displayName;
}
@Override
public String getName() {
return "CurseForge/" + mod.name + '/' + current.displayName;
}
@Override
public boolean equals(ModSource other) {
return other instanceof CurseforgeModSource cu && cu.projectId == projectId && cu.fileId == fileId;

View File

@ -37,6 +37,11 @@ public record DirectModSource(String fileName, String url, Set<ModSource> depend
return "";
}
@Override
public String getName() {
return "Direct";
}
@Override
public boolean equals(ModSource other) {
return false;

View File

@ -10,5 +10,6 @@ public interface ModSource {
Set<ModSource> getDependencies() throws IOException;
Optional<ModSource> getUpdate(String gameVersion) throws IOException;
String getVersion();
String getName();
boolean equals(ModSource other);
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.inceptum.model.inceptum.source;
import io.gitlab.jfronny.inceptum.model.modrinth.ModrinthMod;
import io.gitlab.jfronny.inceptum.model.modrinth.ModrinthVersion;
import io.gitlab.jfronny.inceptum.util.HashUtils;
import io.gitlab.jfronny.inceptum.util.Utils;
@ -12,14 +13,15 @@ import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
//TODO auto-discover curseforge page
public final class ModrinthModSource implements ModSource {
private final String versionId;
private final ModrinthVersion current;
private final ModrinthMod mod;
public ModrinthModSource(String versionId) throws IOException {
this.versionId = versionId;
current = ModrinthApi.getVersion(versionId);
this.current = ModrinthApi.getVersion(versionId);
this.mod = ModrinthApi.getMod(getModId());
}
@Override
@ -46,8 +48,7 @@ public final class ModrinthModSource implements ModSource {
ModrinthVersion stable = null;
ModrinthVersion beta = null;
ModrinthVersion latest = null;
for (ModrinthVersion version : ModrinthApi.getVersions(current.mod_id)) {
//TODO sort versions
for (ModrinthVersion version : ModrinthApi.getVersions(getModId())) {
if (version.game_versions.contains(gameVersion) && version.loaders.contains("fabric")) {
if (latest == null) latest = version;
if (version.version_type == ModrinthVersion.VersionType.beta || version.version_type == ModrinthVersion.VersionType.release) {
@ -73,12 +74,21 @@ public final class ModrinthModSource implements ModSource {
return current.version_number;
}
@Override
public String getName() {
return "Modrinth/" + mod.title + '/' + current.name;
}
@Override
public boolean equals(ModSource other) {
return other instanceof ModrinthModSource ms && ms.current.mod_id.equals(current.mod_id);
return other instanceof ModrinthModSource ms && ms.getModId().equals(getModId());
}
public String getVersionId() {
return versionId;
}
public String getModId() {
return current.mod_id;
}
}

View File

@ -14,6 +14,7 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -109,6 +110,12 @@ public class HttpUtils {
throw new IOException("Could not send request", e);
}
if (res.statusCode() == 200) return res.body();
if (res.statusCode() == 302 && url.startsWith("https://edge.forgecdn.net/") && method == Method.GET) {
Optional<String> location = res.headers().firstValue("location");
if (location.isPresent()) {
return HttpUtils.get(location.get())._send(accept, responseBodyHandler);
}
}
throw new IOException("Unexpected return method: " + res.statusCode() + " (URL=" + url + ")\n" + res.body());
}

View File

@ -24,15 +24,19 @@ public class ModsDirScanner implements Closeable {
public ModsDirScanner(Path modsDir, InstanceMeta instance) {
this.modsDir = modsDir;
this.instance = instance;
th = new Thread(this::scanTaskInternal);
this.th = new Thread(this::scanTaskInternal);
}
public void start() {
th.start();
}
public String getGameVersion() {
return instance.getMinecraftVersion();
}
public Set<IWModDescription> getMods() throws IOException {
Set<IWModDescription> mods = new LinkedHashSet<>();
Set<IWModDescription> mods = new TreeSet<>();
if (Files.isDirectory(modsDir)) {
for (Path path : Utils.ls(modsDir)) {
if (!path.toString().endsWith(".imod"))
@ -47,7 +51,7 @@ public class ModsDirScanner implements Closeable {
return descriptions.get(path);
else {
// not yet scanned
return new IWModDescription(path);
return new IWModDescription(path, getGameVersion());
}
}
@ -60,9 +64,9 @@ public class ModsDirScanner implements Closeable {
}
for (Path mods : Utils.ls(modsDir)) {
if (Files.isDirectory(mods)) {
descriptions.put(mods, new IWModDescription(mods));
descriptions.put(mods, new IWModDescription(mods, getGameVersion()));
} else {
if (mods.toString().endsWith(".jar")) {
if (mods.toString().endsWith(".jar") || mods.toString().endsWith(".jar.disabled")) {
Optional<Path> imod = Optional.of(mods.getParent().resolve(mods.getFileName() + ".imod"));
// fabric.mod.json
Optional<FabricModJson> fmj;
@ -72,24 +76,29 @@ public class ModsDirScanner implements Closeable {
: Optional.empty();
}
// mod description
Optional<ModDescription> md = Optional.empty();
//TODO attempt to compare versions across sources
Optional<ModSource> update = Optional.empty();
if (Files.exists(imod.get())) {
try (FileSystem fs = Utils.openZipFile(mods, false)) {
md = Optional.of(Utils.loadObject(imod.get(), ModDescription.class));
for (ModSource source : md.get().sources) {
Optional<ModSource> ms = source.getUpdate(instance.getMinecraftVersion());
if (ms.isEmpty()) continue;
if (update.isEmpty()) update = ms;
}
}
if (!Files.exists(imod.get())) {
Utils.writeObject(imod.get(), ModDescription.of(mods, getGameVersion()));
}
else imod = Optional.empty();
descriptions.put(mods, new IWModDescription(mods, md, fmj, imod, update));
Optional<ModDescription> md = Optional.of(Utils.loadObject(imod.get(), ModDescription.class));
md.get().hydrateUpdates(getGameVersion());
for (ModSource source : md.get().sources.keySet()) {
//Optional<ModSource> ms = source.getUpdate(instance.getMinecraftVersion());
//TODO properly cache
source.getUpdate(getGameVersion());
//if (ms.isEmpty()) continue;
//if (update.isEmpty()) update = ms;
}
descriptions.put(mods, new IWModDescription(mods, md, fmj, imod));
}
else if (!mods.toString().endsWith(".imod")) {
descriptions.put(mods, new IWModDescription(mods));
else if (mods.toString().endsWith(".imod")) {
// remove .imod if the corresponding jar doesn't exist
//TODO ensure this is not called while downloading a pack
String fn = mods.getFileName().toString();
if (!Files.exists(mods.getParent().resolve(fn.substring(0, fn.length() - 5))))
Files.delete(mods);
}
else {
descriptions.put(mods, new IWModDescription(mods, getGameVersion()));
}
}
}
@ -105,7 +114,7 @@ public class ModsDirScanner implements Closeable {
disposed = true;
}
public static record IWModDescription(Path path, Optional<ModDescription> mod, Optional<FabricModJson> fmj, Optional<Path> imod, Optional<ModSource> update) {
public static 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;
@ -138,8 +147,22 @@ public class ModsDirScanner implements Closeable {
* 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, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
public IWModDescription(Path path, String gameVersion) {
this(path, Files.isDirectory(path) ? Optional.empty() : Optional.of(ModDescription.of(path, gameVersion)), Optional.empty(), 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());
}
}
}

View File

@ -4,7 +4,6 @@ import imgui.ImGui;
import imgui.flag.ImGuiTableFlags;
import imgui.type.ImString;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.curseforge.CurseforgeFingerprint;
import io.gitlab.jfronny.inceptum.model.curseforge.CurseforgeMod;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
@ -23,8 +22,8 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class AddModWindow extends Window {
private final ImString query = new ImString("", 128);
@ -42,9 +41,9 @@ public class AddModWindow extends Window {
}
private void refreshMR() throws IOException {
//TODO move to thread maybe
mr = ModrinthApi.search(query.get(), page, instance.getMinecraftVersion());
cf = CurseforgeApi.search(instance.getMinecraftVersion(), query.get(), page, "Popularity");
//TODO move to thread maybe
//if (mr.hits.isEmpty()) page = 0;
}
@ -80,31 +79,43 @@ public class AddModWindow extends Window {
ImGui.tableNextColumn();
ImGui.text(mod.description);
ImGui.tableNextColumn();
//TODO check if already added
if (ImGui.button("Add##" + mod.mod_id)) {
ModrinthVersion stable = null;
ModrinthVersion beta = null;
ModrinthVersion latest = null;
for (ModrinthVersion version : ModrinthApi.getVersions(mod.mod_id)) {
//TODO sort versions
if (version.game_versions.contains(instance.getMinecraftVersion()) && version.loaders.contains("fabric")) {
if (latest == null) latest = version;
if (version.version_type == ModrinthVersion.VersionType.beta || version.version_type == ModrinthVersion.VersionType.release) {
beta = version;
}
if (version.version_type == ModrinthVersion.VersionType.release) {
stable = version;
boolean alreadyPresent = false;
for (ModsDirScanner.IWModDescription mdsMod : mds.getMods()) {
alreadyPresent = mdsMod.mod().isPresent()
&& mdsMod.mod().get().sources.keySet().stream()
.anyMatch(s -> s instanceof ModrinthModSource ms
&& ms.getModId().equals(mod.mod_id));
if (alreadyPresent)
break;
}
if (alreadyPresent) {
ImGui.text("Installed");
}
else {
if (ImGui.button("Add##" + mod.mod_id)) {
ModrinthVersion stable = null;
ModrinthVersion beta = null;
ModrinthVersion latest = null;
for (ModrinthVersion version : ModrinthApi.getVersions(mod.mod_id)) {
if (version.game_versions.contains(instance.getMinecraftVersion()) && version.loaders.contains("fabric")) {
if (latest == null) latest = version;
if (beta == null && (version.version_type == ModrinthVersion.VersionType.beta || version.version_type == ModrinthVersion.VersionType.release)) {
beta = version;
}
if (stable == null && (version.version_type == ModrinthVersion.VersionType.release)) {
stable = version;
}
}
}
}
if (stable != null) beta = stable;
if (beta != null) latest = beta;
if (latest == null) {
Inceptum.showError("No valid version could be identified for this mod", "No version found");
}
else {
//TODO don't block
download(new ModrinthModSource(latest.id), modsDir, mds).write();
if (stable != null) beta = stable;
if (beta != null) latest = beta;
if (latest == null) {
Inceptum.showError("No valid version could be identified for this mod", "No version found");
}
else {
//TODO don't block
download(new ModrinthModSource(latest.id), modsDir, mds).write();
}
}
}
ImGui.sameLine();
@ -125,20 +136,33 @@ public class AddModWindow extends Window {
ImGui.tableNextColumn();
ImGui.text(mod.summary);
ImGui.tableNextColumn();
//TODO check if already added
if (ImGui.button("Add##" + mod.id)) {
CurseforgeMod.GameVersionLatestFile latest = null;
for (CurseforgeMod.GameVersionLatestFile file : mod.gameVersionLatestFiles) {
if (file.gameVersion.equals(instance.getMinecraftVersion())) {
if (latest == null) latest = file;
boolean alreadyPresent = false;
for (ModsDirScanner.IWModDescription mdsMod : mds.getMods()) {
alreadyPresent = mdsMod.mod().isPresent()
&& mdsMod.mod().get().sources.keySet().stream()
.anyMatch(s -> s instanceof CurseforgeModSource ms
&& ms.getProjectId() == mod.id);
if (alreadyPresent)
break;
}
if (alreadyPresent) {
ImGui.text("Installed");
}
else {
if (ImGui.button("Add##" + mod.id)) {
CurseforgeMod.GameVersionLatestFile latest = null;
for (CurseforgeMod.GameVersionLatestFile file : mod.gameVersionLatestFiles) {
if (file.gameVersion.equals(instance.getMinecraftVersion())) {
if (latest == null) latest = file;
}
}
if (latest == null) {
Inceptum.showError("No valid version could be identified for this mod", "No version found");
}
else {
//TODO don't block
download(new CurseforgeModSource(mod.id, latest.projectFileId), modsDir, mds).write();
}
}
if (latest == null) {
Inceptum.showError("No valid version could be identified for this mod", "No version found");
}
else {
//TODO don't block
download(new CurseforgeModSource(mod.id, latest.projectFileId), modsDir, mds).write();
}
}
ImGui.sameLine();
@ -154,47 +178,21 @@ public class AddModWindow extends Window {
}
}
} catch (IOException | URISyntaxException e) {
e.printStackTrace();
Inceptum.LOGGER.error("Something went wrong while rendering an AddModWindow", e);
}
}
public static DownloadMeta download(ModSource ms, Path modsDir, ModsDirScanner mds) throws IOException {
for (ModsDirScanner.IWModDescription value : mds.getMods()) {
if (value.mod().isEmpty()) continue;
for (ModSource source : value.mod().get().sources) {
for (ModSource source : value.mod().get().sources.keySet()) {
if (ms.equals(source)) {
return new DownloadMeta(new ModDownload(value.mod().get().sha1, value.mod().get().murmur2, value.path()), value.mod().get(), source);
}
}
}
ModDownload md = ms.download(modsDir);
ModDescription manifest = new ModDescription();
manifest.sha1 = md.sha1();
manifest.murmur2 = md.murmur2();
manifest.sources = new ArrayList<>();
manifest.sources.add(ms);
if (!(ms instanceof ModrinthModSource)) {
try {
manifest.sources.add(new ModrinthModSource(ModrinthApi.getVersionByHash(manifest.sha1).id));
}
catch (IOException e) {
// not found
}
}
if (!(ms instanceof CurseforgeModSource)) {
try {
CurseforgeFingerprint cf = CurseforgeApi.checkFingerprint(manifest.murmur2);
if (!cf.exactMatches.isEmpty()) {
CurseforgeFingerprint.Mod f = cf.exactMatches.get(0);
manifest.sources.add(new CurseforgeModSource(f.id, f.file.id));
}
}
catch (IOException e) {
// not found
}
}
manifest.dependents = new ArrayList<>();
manifest.dependencies = new ArrayList<>();
ModDescription manifest = ModDescription.of(md.sha1(), md.murmur2(), Optional.of(ms), mds.getGameVersion());
for (ModSource dependency : ms.getDependencies()) {
DownloadMeta depMan = download(dependency, modsDir, mds);
depMan.description.dependents.add(md.file().getFileName().toString());

View File

@ -7,6 +7,7 @@ import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.InceptumGui;
import io.gitlab.jfronny.inceptum.install.Steps;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import io.gitlab.jfronny.inceptum.util.JvmUtils;
import io.gitlab.jfronny.inceptum.util.ModsDirScanner;
import io.gitlab.jfronny.inceptum.util.Utils;
@ -15,6 +16,9 @@ import io.gitlab.jfronny.inceptum.windows.control.InstanceManageControls;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class InstanceEditWindow extends Window {
private final Path path;
@ -83,7 +87,9 @@ public class InstanceEditWindow extends Window {
ImGui.text("Did you know that every instance in Inceptum is a git repository?");
ImGui.endTabItem();
}
//TODO update all
//TODO update all/better updatability indicator
//TODO there is still noticeable lag here
//TODO drag-and-drop mods
if (instance.isFabric() && ImGui.beginTabItem("Mods")) {
if (!Files.exists(path.resolve("mods"))) {
try {
@ -107,9 +113,24 @@ public class InstanceEditWindow extends Window {
ImGui.separator();
try {
for (ModsDirScanner.IWModDescription mod : mds.getMods()) {
if (ImGui.button(mod.getName())) {
selected = mod.path();
boolean wasEnabled = !mod.path().toString().endsWith(".disabled");
if (ImGui.checkbox("##" + mod.getName(), wasEnabled)) {
String fName = mod.path().getFileName().toString();
final String disabledSuffix = ".disabled";
if (fName.endsWith(disabledSuffix))
fName = fName.substring(0, fName.length() - disabledSuffix.length());
if (wasEnabled) fName += disabledSuffix;
Path newSel = mod.path().getParent().resolve(fName);
try {
Files.move(mod.path(), newSel);
if (mod.path().equals(selected)) selected = newSel;
mod = new ModsDirScanner.IWModDescription(newSel, mod);
} catch (IOException e) {
Inceptum.showError("Could not change disabled state", e);
}
}
ImGui.sameLine();
if (ImGui.button(mod.getName())) selected = mod.path();
}
} catch (IOException e) {
e.printStackTrace();
@ -126,15 +147,32 @@ public class InstanceEditWindow extends Window {
for (String s : md.getDescription()) {
ImGui.text(s);
}
if (md.update().isPresent() && ImGui.button("Update to " + md.update().get().getVersion())) {
try {
AddModWindow.DownloadMeta dm = AddModWindow.download(md.update().get(), path.resolve("mods"), mds);
dm.write();
Files.delete(md.path());
if (md.imod().isPresent() && Files.exists(md.imod().get())) Files.delete(md.imod().get());
selected = dm.download().file();
} catch (IOException e) {
Inceptum.showError("Update failed", e);
ImGui.separator();
if (md.mod().isPresent()) {
Map<ModSource, Optional<ModSource>> sources = md.mod().get().sources;
ImGui.text("Sources:");
if (sources.isEmpty())
ImGui.bulletText("Local Drive");
else {
for (Map.Entry<ModSource, Optional<ModSource>> source : sources.entrySet()) {
ImGui.bulletText(source.getKey().getName());
Optional<ModSource> ms = source.getValue();
if (ms.isPresent()) {
ImGui.sameLine();
if (ImGui.button("Update to " + ms.get().getVersion())) {
try {
AddModWindow.DownloadMeta dm = AddModWindow.download(ms.get(), path.resolve("mods"), mds);
dm.write();
Files.delete(md.path());
if (md.imod().isPresent() && Files.exists(md.imod().get()))
Files.delete(md.imod().get());
selected = dm.download().file();
} catch (IOException e) {
Inceptum.showError("Update failed", e);
}
}
}
}
}
}
if (ImGui.button("Delete")) {