Close Files.list streams, don't block for mods dir scan

This commit is contained in:
JFronny 2021-11-04 11:49:40 +01:00
parent 0ac146e3ab
commit e6e6c5822f
No known key found for this signature in database
GPG Key ID: BEC5ACBBD4EE17E5
17 changed files with 369 additions and 199 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@
build/
run/
imgui.ini
hs_err_pid*
hs_err_pid*
inceptum.log

View File

@ -30,7 +30,7 @@ public class Steps {
);
public static void reDownload() throws IOException {
Files.list(Inceptum.INSTANCE_DIR).forEach(p -> {
Utils.ls(Inceptum.INSTANCE_DIR, p -> {
try {
reDownload(p);
} catch (IOException e) {

View File

@ -43,7 +43,7 @@ public class DownloadLibrariesStep implements Step {
currentState.set("Extracting natives");
Inceptum.LOGGER.info(currentState.get());
try (FileSystem libFs = Utils.openZipFile(path, false)) {
for (Path path1 : Files.list(libFs.getPath(".")).toList()) {
for (Path path1 : Utils.ls(libFs.getPath("."))) {
Utils.copyRecursive(path1, Inceptum.NATIVES_DIR.resolve(InstanceMeta.getMinecraftVersion(version.id)));
}
}

View File

@ -18,13 +18,16 @@ public class SetupDirsStep implements Step {
Path iDir = Inceptum.INSTANCE_DIR.resolve(info.name());
if (!Files.exists(iDir)) {
Files.createDirectories(iDir.resolve("inceptum.setup.lock"));
Files.createDirectories(iDir.resolve("config"));
if (info.loader().type() != LoaderInfo.Type.None)
Files.createDirectories(iDir.resolve("mods"));
Files.createDirectories(iDir.resolve("resourcepacks"));
Files.createDirectories(iDir.resolve("saves"));
Files.createDirectories(iDir.resolve("screenshots"));
}
if (info.loader().type() != LoaderInfo.Type.None) {
if (!Files.exists(iDir.resolve("mods")))
Files.createDirectories(iDir.resolve("mods"));
if (!Files.exists(iDir.resolve("config")))
Files.createDirectories(iDir.resolve("config"));
}
Files.createDirectories(Inceptum.NATIVES_DIR.resolve(InstanceMeta.getMinecraftVersion(info.version().id)));
}
}

View File

@ -27,7 +27,7 @@ public final class CurseforgeModSource implements ModSource {
@Override
public ModDownload download(Path modsDir) throws IOException {
//TODO test
//TODO this doesn't work
Path path = modsDir.resolve(current.fileName);
Utils.downloadFile(current.downloadUrl.replace("edge.forgecdn.net", "media.forgecdn.net"), path);
byte[] data = Files.readAllBytes(path);

View File

@ -98,39 +98,37 @@ public class HttpUtils {
return this;
}
private <T> T _send(String accept, HttpResponse.BodyHandler<T> responseBodyHandler) {
private <T> T _send(String accept, HttpResponse.BodyHandler<T> responseBodyHandler) throws IOException {
builder.header("Accept", accept);
if (method != null) builder.method(method.name(), HttpRequest.BodyPublishers.noBody());
HttpResponse<T> res;
try {
var res = CLIENT.send(builder.build(), responseBodyHandler);
if (res.statusCode() == 200) return res.body();
Inceptum.LOGGER.error("Unexpected return method: " + res.statusCode() + " (URL=" + url + ")");
Inceptum.LOGGER.error(getString(res.body()));
return null;
} catch (IOException | InterruptedException e) {
Inceptum.LOGGER.error("Could not send request", e);
return null;
res = CLIENT.send(builder.build(), responseBodyHandler);
} catch (InterruptedException e) {
throw new IOException("Could not send request", e);
}
if (res.statusCode() == 200) return res.body();
throw new IOException("Unexpected return method: " + res.statusCode() + " (URL=" + url + ")\n" + res.body());
}
public void send() {
public void send() throws IOException {
_send("*/*", HttpResponse.BodyHandlers.discarding());
}
public InputStream sendInputStream() {
public InputStream sendInputStream() throws IOException {
return _send("*/*", HttpResponse.BodyHandlers.ofInputStream());
}
public String sendString() {
public String sendString() throws IOException {
return _send("*/*", HttpResponse.BodyHandlers.ofString());
}
public Stream<String> sendLines() {
public Stream<String> sendLines() throws IOException {
return _send("*/*", HttpResponse.BodyHandlers.ofLines());
}
public <T> T sendJson(Type type) {
public <T> T sendJson(Type type) throws IOException {
InputStream in = _send("application/json", HttpResponse.BodyHandlers.ofInputStream());
return in == null ? null : Inceptum.GSON.fromJson(new InputStreamReader(in), type);
}

View File

@ -0,0 +1,145 @@
package io.gitlab.jfronny.inceptum.util;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.fabric.FabricModJson;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import java.io.Closeable;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
public class ModsDirScanner implements Closeable {
private final Map<Path, IWModDescription> descriptions = new HashMap<>();
private final Thread th;
private final Path modsDir;
private final InstanceMeta instance;
private boolean disposed = false;
public ModsDirScanner(Path modsDir, InstanceMeta instance) {
this.modsDir = modsDir;
this.instance = instance;
th = new Thread(this::scanTaskInternal);
}
public void start() {
th.start();
}
public Set<IWModDescription> getMods() throws IOException {
Set<IWModDescription> mods = new LinkedHashSet<>();
if (Files.isDirectory(modsDir)) {
for (Path path : Utils.ls(modsDir)) {
if (!path.toString().endsWith(".imod"))
mods.add(get(path));
}
}
return mods;
}
public IWModDescription get(Path path) {
if (descriptions.containsKey(path))
return descriptions.get(path);
else {
// not yet scanned
return new IWModDescription(path);
}
}
private void scanTaskInternal() {
while (!disposed) {
try {
if (!Files.isDirectory(modsDir)) {
Thread.sleep(5000);
continue;
}
for (Path mods : Utils.ls(modsDir)) {
if (Files.isDirectory(mods)) {
descriptions.put(mods, new IWModDescription(mods));
} else {
if (mods.toString().endsWith(".jar")) {
Optional<Path> imod = Optional.of(mods.getParent().resolve(mods.getFileName() + ".imod"));
// fabric.mod.json
Optional<FabricModJson> fmj;
try (FileSystem fs = Utils.openZipFile(mods, false)) {
fmj = Files.exists(fs.getPath("fabric.mod.json"))
? Optional.of(Utils.loadObject(fs.getPath("fabric.mod.json"), FabricModJson.class))
: 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;
}
}
}
else imod = Optional.empty();
descriptions.put(mods, new IWModDescription(mods, md, fmj, imod, update));
}
else if (!mods.toString().endsWith(".imod")) {
descriptions.put(mods, new IWModDescription(mods));
}
}
}
Thread.sleep(100);
} catch (IOException | URISyntaxException | InterruptedException e) {
Inceptum.LOGGER.error("Could not list mods", e);
}
}
}
@Override
public void close() {
disposed = true;
}
public static record IWModDescription(Path path, Optional<ModDescription> mod, Optional<FabricModJson> fmj, Optional<Path> imod, Optional<ModSource> update) {
public String getName() {
if (fmj.isEmpty()) return path.getFileName().toString();
String base;
if (fmj.get().name != null) base = fmj.get().name;
else if (fmj.get().id != null) base = fmj.get().id;
else {
base = path.getFileName().toString();
if (Files.isDirectory(path))
base += '/';
}
if (fmj.get().version != null) base += ' ' + fmj.get().version;
return base;
}
public String[] getDescription() {
try {
if (fmj.isPresent() && fmj.get().description != null) {
return fmj.get().description.split("\n");
}
else if (Files.isDirectory(path)) {
return Utils.lsVi(path);
}
} catch (IOException e) {
Inceptum.LOGGER.error("Could not get description", e);
}
return new String[] {"No description is available", "This mod may have been added manually"};
}
/**
* 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());
}
}
}

View File

@ -3,7 +3,7 @@ package io.gitlab.jfronny.inceptum.util;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.OSType;
import java.awt.*;
import java.awt.Desktop;
import java.io.*;
import java.lang.reflect.Type;
import java.net.URI;
@ -14,9 +14,13 @@ import java.nio.file.FileSystem;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class Utils {
public static final Pattern VALID_FILENAME = Pattern.compile("[a-zA-Z0-9_\\-.][a-zA-Z0-9 _\\-.]*[a-zA-Z0-9_\\-.]");
@ -107,7 +111,7 @@ public class Utils {
public static void clearDirectory(Path path) throws IOException {
if (!Files.exists(path)) return;
try {
Files.list(path).forEach(p -> {
Utils.ls(path, p -> {
if (Files.isDirectory(p)) {
try {
deleteRecursive(p);
@ -129,23 +133,26 @@ public class Utils {
}
public static void deleteRecursive(Path path) throws IOException {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
FileVisitResult fv = super.visitFile(file, attrs);
if (fv != FileVisitResult.CONTINUE) return fv;
Files.delete(file);
return FileVisitResult.CONTINUE;
}
if (Files.isDirectory(path)) {
Files.walkFileTree(path, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
FileVisitResult fv = super.visitFile(file, attrs);
if (fv != FileVisitResult.CONTINUE) return fv;
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
FileVisitResult fv = super.postVisitDirectory(dir, exc);
if (fv != FileVisitResult.CONTINUE) return fv;
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
FileVisitResult fv = super.postVisitDirectory(dir, exc);
if (fv != FileVisitResult.CONTINUE) return fv;
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
else Files.delete(path);
}
public static void copyRecursive(Path source, Path destination) throws IOException {
@ -155,7 +162,7 @@ public class Utils {
public static void copyRecursive(Path source, Path destination, CopyOption... copyOptions) throws IOException {
if (!Files.exists(destination)) Files.createDirectories(destination);
if(Files.isDirectory(source)) {
Files.list(source).forEach(sourcePath -> {
Utils.ls(source, sourcePath -> {
try {
copyRecursive(sourcePath, destination.resolve(sourcePath.getFileName().toString()), copyOptions);
} catch (IOException e) {
@ -176,7 +183,7 @@ public class Utils {
public static void openWebBrowser(URI uri) {
try {
if (OSCheck.OS == OSType.LINUX && JvmUtils.executableInPath("xdg-open")) {
Runtime.getRuntime().exec("xdg-open " + uri);
Runtime.getRuntime().exec(new String[]{"xdg-open", uri.toString()});
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().browse(uri);
}
@ -185,6 +192,42 @@ public class Utils {
}
}
public static void openFile(File file) {
try {
if (OSCheck.OS == OSType.LINUX && JvmUtils.executableInPath("xdg-open")) {
Runtime.getRuntime().exec(new String[]{"xdg-open", file.getAbsoluteFile().toString()});
} else if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
Desktop.getDesktop().open(file);
}
} catch (Exception e) {
Inceptum.LOGGER.error("Error opening web browser!", e);
}
}
public static List<Path> ls(Path dir) throws IOException {
try (Stream<Path> sp = Files.list(dir)) {
return sp.toList();
}
}
public static String[] lsVi(Path dir) throws IOException {
try (Stream<Path> sp = Files.list(dir)) {
return sp.map(p -> Files.isDirectory(p) ? p.getFileName().toString() + "/" : p.getFileName().toString()).toArray(String[]::new);
}
}
public static List<Path> ls(Path dir, Predicate<Path> predicate) throws IOException {
try (Stream<Path> sp = Files.list(dir); Stream<Path> fi = sp.filter(predicate)) {
return fi.toList();
}
}
public static void ls(Path dir, Consumer<Path> consumer) throws IOException {
try (Stream<Path> sp = Files.list(dir)) {
sp.forEach(consumer);
}
}
public static FileSystem openZipFile(Path zip, boolean create) throws IOException, URISyntaxException {
URI fileUri = zip.toUri();
return FileSystems.newFileSystem(new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null), create ? Map.of("create", "true") : Map.of());

View File

@ -39,7 +39,7 @@ public class CurseforgeApi {
return Utils.downloadObject(API_URL + "addon/" + modId + "/file/" + fileId, CurseforgeFile.class);
}
public static CurseforgeFingerprint checkFingerprint(String hash) {
public static CurseforgeFingerprint checkFingerprint(String hash) throws IOException {
return HttpUtils.post(API_URL + "fingerprint").bodyJson("[" + hash + "]").sendJson(CurseforgeFingerprint.class);
}
}

View File

@ -6,9 +6,9 @@ import io.gitlab.jfronny.inceptum.model.microsoft.LoginResponse;
import io.gitlab.jfronny.inceptum.model.microsoft.OauthTokenResponse;
import io.gitlab.jfronny.inceptum.model.microsoft.Profile;
import io.gitlab.jfronny.inceptum.model.microsoft.XboxLiveAuthResponse;
import io.gitlab.jfronny.inceptum.windows.AlertWindow;
import io.gitlab.jfronny.inceptum.windows.MicrosoftLoginWindow;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Optional;
@ -88,31 +88,24 @@ public class MicrosoftAccount {
return accessToken;
}
public String getCurrentUsername() {
public String getCurrentUsername() throws IOException {
Profile profile = MicrosoftAuthAPI.getMcProfile(accessToken);
if (profile == null) {
Inceptum.LOGGER.error("Error getting Minecraft profile");
return null;
}
return Optional.of(profile.name).orElse(null);
if (profile.name == null) throw new IOException("Got null name");
return profile.name;
}
public void updateSkinPreCheck() {
this.refreshAccessToken();
}
public String getSkinUrl() {
public String getSkinUrl() throws IOException {
Profile profile = MicrosoftAuthAPI.getMcProfile(accessToken);
if (profile == null) {
Inceptum.LOGGER.error("Error getting Minecraft profile");
return null;
}
return Optional.of(profile.skins).orElse(new ArrayList<>()).stream()
.filter(s -> s.state.equalsIgnoreCase("ACTIVE")).findFirst().map(s -> s.url).orElse(null);
return Optional.of(profile.skins).orElse(new ArrayList<>())
.stream()
.filter(s -> s.state.equalsIgnoreCase("ACTIVE"))
.findFirst()
.map(s -> s.url)
.orElse(null);
}
public boolean refreshAccessToken() {

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.inceptum.util.api.account;
import io.gitlab.jfronny.inceptum.model.microsoft.*;
import io.gitlab.jfronny.inceptum.util.HttpUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -28,7 +29,7 @@ public class MicrosoftAuthAPI {
public static final String MICROSOFT_MINECRAFT_STORE_URL = "https://api.minecraftservices.com/entitlements/mcstore";
public static final String MICROSOFT_MINECRAFT_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile";
public static OauthTokenResponse tradeCodeForAccessToken(String code) {
public static OauthTokenResponse tradeCodeForAccessToken(String code) throws IOException {
return HttpUtils.post(MICROSOFT_AUTH_TOKEN_URL)
.bodyForm(Map.of("client_id", MICROSOFT_LOGIN_CLIENT_ID,
"code", code,
@ -38,7 +39,7 @@ public class MicrosoftAuthAPI {
.sendJson(OauthTokenResponse.class);
}
public static OauthTokenResponse refreshAccessToken(String refreshToken) {
public static OauthTokenResponse refreshAccessToken(String refreshToken) throws IOException {
return HttpUtils.post(MICROSOFT_AUTH_TOKEN_URL)
.bodyForm(Map.of("client_id", MICROSOFT_LOGIN_CLIENT_ID,
"refresh_token", refreshToken,
@ -47,7 +48,7 @@ public class MicrosoftAuthAPI {
.sendJson(OauthTokenResponse.class);
}
public static XboxLiveAuthResponse getXBLToken(String accessToken) {
public static XboxLiveAuthResponse getXBLToken(String accessToken) throws IOException {
Map<Object, Object> properties = new HashMap<>();
properties.put("AuthMethod", "RPS");
properties.put("SiteName", "user.auth.xboxlive.com");
@ -64,7 +65,7 @@ public class MicrosoftAuthAPI {
.sendJson(XboxLiveAuthResponse.class);
}
public static XboxLiveAuthResponse getXstsToken(String xblToken) {
public static XboxLiveAuthResponse getXstsToken(String xblToken) throws IOException {
Map<Object, Object> properties = new HashMap<>();
properties.put("SandboxId", "RETAIL");
@ -83,7 +84,7 @@ public class MicrosoftAuthAPI {
.sendJson(XboxLiveAuthResponse.class);
}
public static LoginResponse loginToMinecraft(String xstsToken) {
public static LoginResponse loginToMinecraft(String xstsToken) throws IOException {
Map<Object, Object> data = new HashMap<Object, Object>();
data.put("identityToken", xstsToken);
@ -92,11 +93,11 @@ public class MicrosoftAuthAPI {
.sendJson(LoginResponse.class);
}
public static Store getMcEntitlements(String accessToken) {
public static Store getMcEntitlements(String accessToken) throws IOException {
return HttpUtils.get(MICROSOFT_MINECRAFT_STORE_URL).bearer(accessToken).sendJson(Store.class);
}
public static Profile getMcProfile(String accessToken) {
public static Profile getMcProfile(String accessToken) throws IOException {
return HttpUtils.get(MICROSOFT_MINECRAFT_PROFILE_URL).bearer(accessToken).sendJson(Profile.class);
}
}

View File

@ -14,6 +14,7 @@ import io.gitlab.jfronny.inceptum.model.inceptum.source.ModSource;
import io.gitlab.jfronny.inceptum.model.inceptum.source.ModrinthModSource;
import io.gitlab.jfronny.inceptum.model.modrinth.ModrinthSearchResult;
import io.gitlab.jfronny.inceptum.model.modrinth.ModrinthVersion;
import io.gitlab.jfronny.inceptum.util.ModsDirScanner;
import io.gitlab.jfronny.inceptum.util.Utils;
import io.gitlab.jfronny.inceptum.util.api.CurseforgeApi;
import io.gitlab.jfronny.inceptum.util.api.ModrinthApi;
@ -24,21 +25,20 @@ import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class AddModWindow extends Window {
private final ImString query = new ImString("", 128);
private final Path modsDir;
private final InstanceMeta instance;
private final Map<Path, InstanceEditWindow.IWModDescription> descriptions;
private final ModsDirScanner mds;
private int page = 0;
private ModrinthSearchResult mr = null;
private List<CurseforgeMod> cf = null;
public AddModWindow(Path modsDir, InstanceMeta instance, Map<Path, InstanceEditWindow.IWModDescription> descriptions) {
public AddModWindow(Path modsDir, InstanceMeta instance, ModsDirScanner mds) {
super(modsDir.getParent().getFileName().toString() + " - Add Mods");
this.modsDir = modsDir;
this.instance = instance;
this.descriptions = descriptions;
this.mds = mds;
}
private void refreshMR() throws IOException {
@ -103,7 +103,8 @@ public class AddModWindow extends Window {
Inceptum.showError("No valid version could be identified for this mod", "No version found");
}
else {
download(new ModrinthModSource(latest.id), modsDir, descriptions).write();
//TODO don't block
download(new ModrinthModSource(latest.id), modsDir, mds).write();
}
}
ImGui.sameLine();
@ -136,7 +137,8 @@ public class AddModWindow extends Window {
Inceptum.showError("No valid version could be identified for this mod", "No version found");
}
else {
download(new CurseforgeModSource(mod.id, latest.projectFileId), modsDir, descriptions).write();
//TODO don't block
download(new CurseforgeModSource(mod.id, latest.projectFileId), modsDir, mds).write();
}
}
ImGui.sameLine();
@ -156,11 +158,12 @@ public class AddModWindow extends Window {
}
}
public static DownloadMeta download(ModSource ms, Path modsDir, Map<Path, InstanceEditWindow.IWModDescription> descriptions) throws IOException {
for (InstanceEditWindow.IWModDescription value : descriptions.values()) {
for (ModSource source : value.mod().sources) {
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) {
if (ms.equals(source)) {
return new DownloadMeta(new ModDownload(value.mod().sha1, value.mod().murmur2, value.path()), value.mod(), source);
return new DownloadMeta(new ModDownload(value.mod().get().sha1, value.mod().get().murmur2, value.path()), value.mod().get(), source);
}
}
}
@ -193,7 +196,7 @@ public class AddModWindow extends Window {
manifest.dependents = new ArrayList<>();
manifest.dependencies = new ArrayList<>();
for (ModSource dependency : ms.getDependencies()) {
DownloadMeta depMan = download(dependency, modsDir, descriptions);
DownloadMeta depMan = download(dependency, modsDir, mds);
depMan.description.dependents.add(md.file().getFileName().toString());
manifest.dependencies.add(depMan.download.file().getFileName().toString());
depMan.write();

View File

@ -6,22 +6,15 @@ import imgui.type.ImString;
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.fabric.FabricModJson;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.model.inceptum.ModDescription;
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;
import io.gitlab.jfronny.inceptum.windows.control.InstanceManageControls;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class InstanceEditWindow extends Window {
private final Path path;
@ -29,34 +22,27 @@ public class InstanceEditWindow extends Window {
private final InstanceManageControls imc;
private final ImBoolean customJava;
private final ImString customJavaPath = new ImString(128);
private final Map<Path, IWModDescription> descriptions = new HashMap<>();
private final ModsDirScanner mds;
private Path selected = null;
private boolean reDownload = false;
public static record IWModDescription(ModDescription mod, FabricModJson fmj, Path path, Path imod, Optional<ModSource> update) {
public String getName() {
if (fmj == null) return path.getFileName().toString();
String base;
if (fmj.name != null) base = fmj.name;
else if (fmj.id != null) base = fmj.id;
else base = path.getFileName().toString();
if (fmj.version != null) base += ' ' + fmj.version;
return base;
}
}
public InstanceEditWindow(Path path, InstanceMeta instance) {
super(path.getFileName().toString() + " - Edit");
this.path = path;
this.instance = instance;
mds = new ModsDirScanner(path.resolve("mods"), instance);
mds.start();
customJava = new ImBoolean(instance.java != null);
imc = new InstanceManageControls(instance.getLoaderVersion());
imc = new InstanceManageControls(instance);
}
@Override
public void draw() {
if (ImGui.beginTabBar("InstanceEdit" + path)) {
if (ImGui.beginTabItem("General")) {
if (ImGui.button("Open Directory")) {
Utils.openFile(path.toFile());
}
imc.nameBox("Rename", name -> {
try {
Path newPath = Inceptum.INSTANCE_DIR.resolve(name);
@ -98,50 +84,35 @@ public class InstanceEditWindow extends Window {
ImGui.endTabItem();
}
//TODO update all
if (instance.isFabric() && Files.exists(path.resolve("mods")) && ImGui.beginTabItem("Mods")) {
if (instance.isFabric() && ImGui.beginTabItem("Mods")) {
if (!Files.exists(path.resolve("mods"))) {
try {
Files.createDirectories(path.resolve("mods"));
} catch (IOException e) {
Inceptum.LOGGER.error("Could not create mods directory which was missing from this modded instance", e);
}
}
ImGui.beginChild("mods select", 200, 0);
if (ImGui.button("Add")) {
InceptumGui.WINDOWS.add(new AddModWindow(path.resolve("mods"), instance, descriptions));
InceptumGui.WINDOWS.add(new AddModWindow(path.resolve("mods"), instance, mds));
}
ImGui.sameLine();
if (Files.exists(path.resolve("mods")) && ImGui.button("Show")) {
Utils.openFile(path.resolve("mods").toFile());
}
ImGui.sameLine();
if (Files.exists(path.resolve("config")) && ImGui.button("Configs")) {
Utils.openFile(path.resolve("config").toFile());
}
ImGui.separator();
try {
for (Path mods : Files.list(path.resolve("mods")).toList()) {
if (Files.isDirectory(mods)) {
ImGui.text(mods.getFileName().toString());
}
else {
if (mods.toString().endsWith(".jar")) {
Path imod = mods.getParent().resolve(mods.getFileName() + ".imod");
//TODO prevent blocking UI thread
if (Files.exists(imod) && !descriptions.containsKey(mods)) {
try (FileSystem fs = Utils.openZipFile(mods, false)) {
ModDescription md = Utils.loadObject(imod, ModDescription.class);
Optional<ModSource> update = Optional.empty();
for (ModSource source : md.sources) {
Optional<ModSource> ms = source.getUpdate(instance.getMinecraftVersion());
if (ms.isEmpty()) continue;
if (update.isEmpty()) update = ms;
}
if (Files.exists(fs.getPath("fabric.mod.json"))) {
descriptions.put(mods, new IWModDescription(md,
Utils.loadObject(fs.getPath("fabric.mod.json"), FabricModJson.class),
mods,
imod,
update));
}
else {
descriptions.put(mods, new IWModDescription(md, null, mods, null, update));
}
}
}
}
if (!mods.toString().endsWith(".imod") && ImGui.button(descriptions.containsKey(mods) ? descriptions.get(mods).getName() : mods.getFileName().toString())) {
selected = mods;
}
for (ModsDirScanner.IWModDescription mod : mds.getMods()) {
if (ImGui.button(mod.getName())) {
selected = mod.path();
}
}
} catch (IOException | URISyntaxException e) {
Inceptum.showError("Could not list mods", e);
} catch (IOException e) {
e.printStackTrace();
}
ImGui.endChild();
ImGui.sameLine();
@ -149,38 +120,34 @@ public class InstanceEditWindow extends Window {
if (selected == null) {
ImGui.text("Select a mod to view settings");
} else {
if (descriptions.containsKey(selected)) {
IWModDescription md = descriptions.get(selected);
ImGui.text(md.getName());
if (md.fmj != null && md.fmj.description != null) {
ImGui.text(md.fmj.description);
}
if (md.update.isPresent() && ImGui.button("Update to " + md.update.get().getVersion())) {
try {
AddModWindow.download(md.update.get(), path.resolve("mods"), descriptions).write();
Files.delete(md.path);
if (md.imod != null && Files.exists(md.imod)) Files.delete(md.imod);
} catch (IOException e) {
Inceptum.showError("Update failed", e);
}
}
if (ImGui.button("Delete")) {
if (!md.mod.dependents.isEmpty()) {
Inceptum.showError("This mod still has the following dependent mods installed: " + String.join(", ", md.mod.dependents), "Dependents present");
}
else {
try {
delete(md);
} catch (IOException e) {
Inceptum.showError("Couldn't delete the file", e);
}
selected = null;
}
ModsDirScanner.IWModDescription md = mds.get(selected);
ImGui.text(md.getName());
ImGui.separator();
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);
}
}
else {
ImGui.text("This mod was added manually");
ImGui.text("Its hash was also not found on modrinth or curseforge.");
if (ImGui.button("Delete")) {
if (md.mod().isPresent() && !md.mod().get().dependents.isEmpty())
Inceptum.showError("This mod still has the following dependent mods installed: " + String.join(", ", md.mod().get().dependents), "Dependents present");
else {
try {
delete(md);
} catch (IOException e) {
Inceptum.showError("Couldn't delete the file", e);
}
selected = null;
}
}
}
ImGui.endGroup();
@ -190,15 +157,20 @@ public class InstanceEditWindow extends Window {
}
}
private void delete(IWModDescription md) throws IOException {
Files.delete(md.path);
if (md.imod != null && Files.exists(md.imod)) Files.delete(md.imod);
for (String dependency : md.mod.dependencies) {
Path dep = path.resolve("mods").resolve(dependency);
IWModDescription dmd = descriptions.get(dep);
dmd.mod.dependencies.remove(md.path.getFileName().toString());
if (dmd.mod.dependencies.isEmpty()) delete(dmd);
else Utils.writeObject(dmd.imod, dmd.mod);
private void delete(ModsDirScanner.IWModDescription md) throws IOException {
Utils.deleteRecursive(md.path());
if (md.imod().isPresent() && Files.exists(md.imod().get())) Files.delete(md.imod().get());
if (md.mod().isPresent()) {
for (String dependency : md.mod().get().dependencies) {
Path dep = path.resolve("mods").resolve(dependency);
ModsDirScanner.IWModDescription dmd = mds.get(dep);
if (dmd.mod().isPresent()) {
dmd.mod().get().dependencies.remove(md.path().getFileName().toString());
if (dmd.mod().get().dependencies.isEmpty()) delete(dmd);
else if (dmd.imod().isPresent())
Utils.writeObject(dmd.imod().get(), dmd.mod().get());
}
}
}
}
@ -213,8 +185,10 @@ public class InstanceEditWindow extends Window {
@Override
public void close() {
super.close();
mds.close();
if (reDownload) {
try {
//TODO show progress
Steps.reDownload(path);
} catch (IOException e) {
Inceptum.showError("Could not re-download data", e);

View File

@ -6,17 +6,9 @@ import imgui.type.ImBoolean;
import imgui.type.ImInt;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.InceptumGui;
import io.gitlab.jfronny.inceptum.install.SetupStepInfo;
import io.gitlab.jfronny.inceptum.install.Step;
import io.gitlab.jfronny.inceptum.install.Steps;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.model.inceptum.LoaderInfo;
import io.gitlab.jfronny.inceptum.model.mojang.VersionInfo;
import io.gitlab.jfronny.inceptum.model.mojang.VersionsListInfo;
import io.gitlab.jfronny.inceptum.util.MapAppender;
import io.gitlab.jfronny.inceptum.util.Utils;
import io.gitlab.jfronny.inceptum.util.api.FabricMetaApi;
import io.gitlab.jfronny.inceptum.util.api.McApi;
import io.gitlab.jfronny.inceptum.util.api.account.AccountManager;
import io.gitlab.jfronny.inceptum.util.api.account.AuthInfo;
import io.gitlab.jfronny.inceptum.util.api.account.MicrosoftAccount;
@ -26,8 +18,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
public class MainWindow extends Window {
private final ImBoolean darkTheme = new ImBoolean(Inceptum.CONFIG.darkTheme);
@ -99,7 +90,7 @@ public class MainWindow extends Window {
List<Path> paths;
try {
if (!Files.exists(Inceptum.INSTANCE_DIR)) Files.createDirectories(Inceptum.INSTANCE_DIR);
paths = Files.list(Inceptum.INSTANCE_DIR).filter(Files::isDirectory).toList();
paths = Utils.ls(Inceptum.INSTANCE_DIR, (Predicate<Path>) Files::isDirectory);
} catch (IOException e) {
Inceptum.LOGGER.error("Could not list instances");
return;

View File

@ -6,6 +6,7 @@ import imgui.type.ImInt;
import imgui.type.ImString;
import io.gitlab.jfronny.inceptum.Inceptum;
import io.gitlab.jfronny.inceptum.model.fabric.FabricVersionLoaderInfo;
import io.gitlab.jfronny.inceptum.model.inceptum.InstanceMeta;
import io.gitlab.jfronny.inceptum.model.inceptum.LoaderInfo;
import io.gitlab.jfronny.inceptum.model.mojang.VersionInfo;
import io.gitlab.jfronny.inceptum.model.mojang.VersionsList;
@ -34,15 +35,21 @@ public class InstanceManageControls {
private VersionsListInfo selected;
private FabricVersionLoaderInfo selectedFabric;
public InstanceManageControls(String loaderVersion) {
public InstanceManageControls(InstanceMeta meta) {
selected = getVersions(false).get(0);
if (Inceptum.CONFIG.snapshots)
version.set(manifest.versions.indexOf(selected));
for (VersionsListInfo ver : getVersions(true)) {
if (ver.id.equals(meta.getMinecraftVersion()))
selected = ver;
}
version.set(getVersions(snapshots.get()).indexOf(selected));
name.set(getDefaultName(selected, fabric.get()));
fabric.set(meta == null || meta.isFabric());
List<FabricVersionLoaderInfo> versions = getFabricLoaderInfo();
for (int i = 0, fabricLoaderInfoSize = versions.size(); i < fabricLoaderInfoSize; i++) {
FabricVersionLoaderInfo version = versions.get(i);
if (((loaderVersion == null || loaderVersion.equals("")) && version.loader.stable) || (version.loader.version.equals(loaderVersion))) {
if (meta != null && meta.isFabric()
? version.loader.version.equals(meta.getLoaderVersion())
: version.loader.stable) {
selectedFabric = version;
fabricVersion.set(i);
break;

View File

@ -2,16 +2,24 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %boldCyan(%-34.-34thread) %red(%10.10X{jda.shard}) %boldGreen(%-15.-15logger{0}) %highlight(%-6level) %msg%n</pattern>
<pattern>%d{HH:mm:ss.SSS} %boldCyan(%thread) %boldGreen(%logger{0}) %highlight(%level) %msg%n</pattern>
</encoder>
</appender>
<appender name="MapAppender" class="io.gitlab.jfronny.inceptum.util.MapAppender">
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>inceptum.log</file>
<append>false</append>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{HH:mm:ss.SSS} %thread %logger{0} %level %msg%n</pattern>
</layout>
</appender>
<appender name="MEMORY" class="io.gitlab.jfronny.inceptum.util.MapAppender">
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="MapAppender" />
<appender-ref ref="FILE" />
<appender-ref ref="MEMORY" />
</root>
</configuration>

View File

@ -16,6 +16,7 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Wrapper {
public static void main(String[] args) throws IOException {
@ -45,11 +46,13 @@ public class Wrapper {
}
Path pathChosen = null;
ComparableVersion chosenVer = new ComparableVersion("0");
for (Path path1 : Files.list(p).toList()) {
ComparableVersion cv = new ComparableVersion(path1.getFileName().toString());
if (cv.compareTo(chosenVer) > 0) {
chosenVer = cv;
pathChosen = path1;
try (Stream<Path> versionDirs = Files.list(p)) {
for (Path path1 : versionDirs.toList()) {
ComparableVersion cv = new ComparableVersion(path1.getFileName().toString());
if (cv.compareTo(chosenVer) > 0) {
chosenVer = cv;
pathChosen = path1;
}
}
}
if (pathChosen == null) {