From c02788536498bc75094298e897ac22b2fd46f8aa Mon Sep 17 00:00:00 2001 From: JFronny Date: Sun, 29 Jan 2023 18:34:33 +0100 Subject: [PATCH] GTK: Start working on instance creation flow --- .../inceptum/cli/commands/ImportCommand.java | 2 +- .../jfronny/inceptum/gtk/GtkMenubar.java | 95 +++++++++++++++---- .../inceptum/gtk/window/MainWindow.java | 5 +- .../gtk/window/NewInstanceWindow.java | 14 +-- .../dialog/ProcessStateWatcherDialog.java | 39 +++----- .../window/settings/instance/ExportTab.java | 5 +- .../src/main/resources/inceptum.properties | 8 +- .../src/main/resources/inceptum_de.properties | 8 +- .../inceptum/imgui/window/GuiUtil.java | 27 +++--- .../imgui/window/NewInstanceWindow.java | 9 +- .../dialog/ProcessStateWatcherWindow.java | 22 ++--- .../inceptum/imgui/window/edit/ExportTab.java | 6 +- .../launcher/system/importer/Importer.java | 26 ++++- .../launcher/system/importer/Importers.java | 29 +++++- .../system/launch/InstanceLauncher.java | 3 +- .../launcher/system/setup/SetupStepInfo.java | 4 + .../inceptum/launcher/system/setup/Step.java | 3 +- .../inceptum/launcher/system/setup/Steps.java | 9 +- .../setup/steps/DownloadAssetsStep.java | 8 +- .../setup/steps/DownloadClientStep.java | 3 +- .../system/setup/steps/DownloadJavaStep.java | 4 +- .../setup/steps/DownloadLibrariesStep.java | 9 +- .../system/setup/steps/RunMdsStep.java | 3 +- .../system/setup/steps/SetupDirsStep.java | 3 +- .../system/setup/steps/WriteMetadataStep.java | 3 +- .../inceptum/launcher/util/ProcessState.java | 9 ++ 26 files changed, 226 insertions(+), 130 deletions(-) diff --git a/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ImportCommand.java b/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ImportCommand.java index 7b15fd6..309735f 100644 --- a/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ImportCommand.java +++ b/launcher-cli/src/main/java/io/gitlab/jfronny/inceptum/cli/commands/ImportCommand.java @@ -19,7 +19,7 @@ public class ImportCommand extends Command { if (args.length == 0) throw new IllegalAccessException("You must specify a pack file"); if (args.length != 1) throw new IllegalAccessException("Too many arguments"); ProcessState state = new ProcessState(); - String name = Importers.importPack(Paths.get(args[0]), state).fileName.toString(); + String name = Importers.importPack(Paths.get(args[0]), state).path().fileName.toString(); System.out.println(OutputColors.GREEN_BOLD + "Imported as " + name + OutputColors.RESET); } } diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/GtkMenubar.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/GtkMenubar.java index b2d3c36..0b29d48 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/GtkMenubar.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/GtkMenubar.java @@ -5,9 +5,11 @@ import io.gitlab.jfronny.inceptum.common.MetaHolder; import io.gitlab.jfronny.inceptum.gtk.window.dialog.MicrosoftLoginDialog; import io.gitlab.jfronny.inceptum.gtk.window.dialog.ProcessStateWatcherDialog; import io.gitlab.jfronny.inceptum.gtk.window.settings.launcher.LauncherSettingsWindow; +import io.gitlab.jfronny.inceptum.launcher.system.importer.Importers; import io.gitlab.jfronny.inceptum.launcher.system.launch.*; import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; -import org.gtk.gtk.Application; +import org.gtk.gio.Menu; +import org.gtk.gtk.*; import io.gitlab.jfronny.commons.ref.R; import io.gitlab.jfronny.inceptum.common.Utils; import io.gitlab.jfronny.inceptum.gtk.menu.MenuBuilder; @@ -21,17 +23,23 @@ import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList; import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; import java.io.IOException; +import java.nio.file.Path; import java.util.*; +import java.util.List; public class GtkMenubar { + public static MenuBuilder newMenu; public static MenuBuilder accountsMenu; public static MenuBuilder launchMenu; public static void create(Application app) { var menu = new MenuBuilder(app); var file = menu.submenu("file"); - file.button("new", () -> new NewInstanceWindow(app).show()); + newMenu = file.submenu("new"); + generateNewMenu(app); file.button("redownload", () -> { ProcessState state = new ProcessState(3 + Steps.STEPS.size() * InstanceList.size(), "Initializing"); ProcessStateWatcherDialog.show( @@ -39,24 +47,24 @@ public class GtkMenubar { "Reloading data", "Could not execute refresh task", state, - cancel -> { + () -> { state.incrementStep("Clearing cache directories"); JFiles.clearDirectory(MetaHolder.ASSETS_DIR); JFiles.clearDirectory(MetaHolder.LIBRARIES_DIR, path -> !path.startsWith(MetaHolder.LIBRARIES_DIR.resolve("io/gitlab/jfronny"))); JFiles.clearDirectory(MetaHolder.NATIVES_DIR, path -> !path.startsWith(MetaHolder.NATIVES_DIR.resolve("forceload"))); JFiles.clearDirectory(MetaHolder.CACHE_DIR); - if (cancel.get()) return; + if (state.isCancelled) return; state.incrementStep("Reloading instance list"); InstanceList.reset(); InstanceList.forEach(instance -> { - if (cancel.get()) return; - Steps.reDownload(instance, state, cancel); + if (state.isCancelled) return; + Steps.reDownload(instance, state); }); - }, R::nop); + }); }); file.button("exit", app::quit); launchMenu = menu.submenu("launch"); - generateLaunchMenu(); + generateLaunchMenu(app); accountsMenu = menu.submenu("account"); generateAccountsMenu(app); var help = menu.submenu("help"); @@ -66,9 +74,62 @@ public class GtkMenubar { }); } - public static void generateLaunchMenu() { - Objects.requireNonNull(launchMenu); - launchMenu.clear(); + public static void generateNewMenu(Application app) { + Objects.requireNonNull(newMenu).clear(); + newMenu.button("new", () -> new NewInstanceWindow(app).show()); + newMenu.button("file", () -> { + FileChooserNative dialog = new FileChooserNative( + I18n.get("menu.file.new.file"), + GtkEnvBackend.INSTANCE.dialogParent, + FileChooserAction.OPEN, + "_" + I18n.get("select"), + "_" + I18n.get("cancel") + ); + var filter = new FileFilter(); + filter.addPattern("*.zip"); + filter.addPattern("*.mrpack"); + dialog.addFilter(filter); + dialog.onResponse(responseId -> { + if (responseId == ResponseType.ACCEPT.value) { + var file = dialog.file.path; + if (file == null) { + LauncherEnv.showError("The path returned by the file dialog is null", "Could not import"); + return; + } + ProcessState state = new ProcessState(Importers.MAX_STEPS, "Initializing"); + ProcessStateWatcherDialog.show( + GtkEnvBackend.INSTANCE.dialogParent, + I18n.get("menu.file.new.file"), + I18n.get("menu.file.new.file.error"), + state, + () -> Importers.importPack(Path.of(file), state) + ); + } + }); + dialog.show(); + }); + newMenu.button("url", () -> { + LauncherEnv.getInput( + I18n.get("menu.file.new.url"), + I18n.get("menu.file.new.url.details"), + (String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor), + s -> { + ProcessState state = new ProcessState(Importers.MAX_STEPS, "Initializing"); + ProcessStateWatcherDialog.show( + GtkEnvBackend.INSTANCE.dialogParent, + I18n.get("menu.file.new.url"), + I18n.get("menu.file.new.url.error"), + state, + () -> Importers.importPack(s, state) + ); + }, + R::nop + ); + }); + } + + public static void generateLaunchMenu(Application app) { + Objects.requireNonNull(launchMenu).clear(); try { InstanceList.forEach(entry -> { launchMenu.literalButton(entry.id() + ".launch", entry.toString(), () -> launch(entry, LaunchType.Client)); @@ -98,13 +159,13 @@ public class GtkMenubar { I18n.get("instance.launch.title"), I18n.get("instance.launch.error"), state, - cancel -> { + () -> { try { - Steps.reDownload(instance, state, cancel); + Steps.reDownload(instance, state); } catch (IOException e) { Utils.LOGGER.error("Could not fetch instance, trying to start anyways", e); } - if (cancel.get()) return; + if (state.isCancelled) return; state.updateStep("Starting Game"); try { if (launchType == LaunchType.Client) InstanceLauncher.launchClient(instance); @@ -112,14 +173,12 @@ public class GtkMenubar { } catch (Throwable e) { LauncherEnv.showError("Could not start instance", e); } - }, - R::nop + } ); } public static void generateAccountsMenu(Application app) { - Objects.requireNonNull(accountsMenu); - accountsMenu.clear(); + Objects.requireNonNull(accountsMenu).clear(); accountsMenu.button("new", () -> new MicrosoftLoginDialog(GtkEnvBackend.INSTANCE.dialogParent).show()); accountsMenu.button("manage", () -> { var window = new LauncherSettingsWindow(app); diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java index 6726f87..1490b0d 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/MainWindow.java @@ -36,8 +36,9 @@ public class MainWindow extends ApplicationWindow { super(app); HeaderBar header = new HeaderBar(); - Button newButton = Button.newFromIconName("list-add-symbolic"); - newButton.onClicked(() -> new NewInstanceWindow(app).show()); + MenuButton newButton = new MenuButton(); + newButton.iconName = "list-add-symbolic"; + newButton.menuModel = GtkMenubar.newMenu.menu; MenuButton accountsButton = new MenuButton(); accountsButton.iconName = "avatar-default-symbolic"; diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/NewInstanceWindow.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/NewInstanceWindow.java index f236f39..5d5db9a 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/NewInstanceWindow.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/NewInstanceWindow.java @@ -1,14 +1,16 @@ package io.gitlab.jfronny.inceptum.gtk.window; -import io.gitlab.jfronny.inceptum.gtk.GtkMain; import org.gtk.gtk.*; -public class NewInstanceWindow extends Window { +public class NewInstanceWindow extends Assistant { public NewInstanceWindow(Application app) { this.application = app; - - child = new Label("This feature has not (yet) been implemented for this UI. Please try the CLI or ImGUI UI"); - - //TODO setup wizard (--> NC Tasks) + { + var initialPage = new Box(Orientation.VERTICAL, 8); + initialPage.append(new Label("Importing instances via this assistant is not yet supported, use the ImGUI")); + + appendPage(initialPage); + setPageType(initialPage, AssistantPageType.INTRO); + } } } diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/dialog/ProcessStateWatcherDialog.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/dialog/ProcessStateWatcherDialog.java index be3cb85..4f2fdb6 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/dialog/ProcessStateWatcherDialog.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/dialog/ProcessStateWatcherDialog.java @@ -1,44 +1,37 @@ package io.gitlab.jfronny.inceptum.gtk.window.dialog; import io.gitlab.jfronny.commons.StringFormatter; -import io.gitlab.jfronny.commons.throwable.ThrowingConsumer; +import io.gitlab.jfronny.commons.throwable.ThrowingRunnable; import io.gitlab.jfronny.inceptum.common.Utils; import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend; import io.gitlab.jfronny.inceptum.gtk.GtkMain; import io.gitlab.jfronny.inceptum.gtk.util.I18n; -import io.gitlab.jfronny.inceptum.launcher.LauncherEnv; import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; import org.gtk.glib.GLib; import org.gtk.gtk.*; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.atomic.AtomicBoolean; public class ProcessStateWatcherDialog extends MessageDialog { private final ProcessState state; - private final Runnable cancel; - private final AtomicBoolean canceled = new AtomicBoolean(false); - private final AtomicBoolean finished = new AtomicBoolean(false); + private boolean finished = false; private State cachedState = null; - public static ProcessStateWatcherDialog show(Window parent, String title, String errorMessage, ProcessState state, ThrowingConsumer executor, @Nullable Runnable cancel) { - ProcessStateWatcherDialog dialog = new ProcessStateWatcherDialog(parent, title, errorMessage, state, executor, cancel); + public static ProcessStateWatcherDialog show(Window parent, String title, String errorMessage, ProcessState state, ThrowingRunnable executor) { + ProcessStateWatcherDialog dialog = new ProcessStateWatcherDialog(parent, title, errorMessage, state, executor); dialog.show(); return dialog; } - public ProcessStateWatcherDialog(Window parent, String title, String errorMessage, ProcessState state, ThrowingConsumer executor, @Nullable Runnable cancel) { + public ProcessStateWatcherDialog(Window parent, String title, String errorMessage, ProcessState state, ThrowingRunnable executor) { //TODO alternate UI: Only show progress bar by default, but have a dropdown to a "console" with the actual steps // this should make visualizing parallelized steps easier super(parent, DialogFlags.MODAL.or(DialogFlags.DESTROY_WITH_PARENT), MessageType.INFO, ButtonsType.NONE, null); this.state = state; - this.cancel = cancel; this.title = title; - if (cancel != null) addButton(I18n.get("cancel"), ResponseType.CANCEL.value); + addButton(I18n.get("cancel"), ResponseType.CANCEL.value); onResponse(responseId -> { switch (ResponseType.of(responseId)) { case CLOSE, CANCEL -> { - canceled.set(true); + state.cancel(); close(); } case DELETE_EVENT -> destroy(); @@ -46,19 +39,14 @@ public class ProcessStateWatcherDialog extends MessageDialog { } }); onCloseRequest(() -> { - if (finished.get()) return false; - if (cancel == null) return true; - canceled.set(true); - cancel.run(); + if (finished) return false; + state.cancel(); return false; }); - onClose(() -> { - if (canceled.get() && cancel != null) cancel.run(); - }); var progress = new ProgressBar(); ((Box) messageArea).append(progress); addTickCallback((widget, clock) -> { - if (finished.get()) return GLib.SOURCE_REMOVE; + if (finished) return GLib.SOURCE_REMOVE; var nc = new State(state); if (!nc.equals(cachedState)) { cachedState = nc; @@ -70,9 +58,10 @@ public class ProcessStateWatcherDialog extends MessageDialog { }, null); new Thread(() -> { try { - executor.accept(canceled); + executor.run(); } catch (Throwable e) { - canceled.set(true); + state.cancel(); + Utils.LOGGER.error(errorMessage, e); GtkEnvBackend.simpleDialog( parent, StringFormatter.toString(e), @@ -83,7 +72,7 @@ public class ProcessStateWatcherDialog extends MessageDialog { null ); } finally { - finished.set(true); + finished = true; GtkMain.schedule(this::close); } }).start(); diff --git a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/ExportTab.java b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/ExportTab.java index 548e973..6f0c9da 100644 --- a/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/ExportTab.java +++ b/launcher-gtk/src/main/java/io/gitlab/jfronny/inceptum/gtk/window/settings/instance/ExportTab.java @@ -75,7 +75,7 @@ public class ExportTab extends SettingsTab { I18n.get("instance.settings.export.dialog.title", exporter.name), I18n.get("instance.settings.export.dialog.error", instance.name), state, - cancel -> { + () -> { exporter.generate(state, instance, path); GtkMain.schedule(() -> { MessageDialog success = new MessageDialog( @@ -100,8 +100,7 @@ public class ExportTab extends SettingsTab { }); success.show(); }); - }, - R::nop + } ); } } diff --git a/launcher-gtk/src/main/resources/inceptum.properties b/launcher-gtk/src/main/resources/inceptum.properties index 06c4cdc..224f238 100644 --- a/launcher-gtk/src/main/resources/inceptum.properties +++ b/launcher-gtk/src/main/resources/inceptum.properties @@ -103,4 +103,10 @@ instance.copy.fail=Could not copy instance instance.kill.fail=Could not kill the Instance instance.kill.prompt=Are you sure? instance.kill.details=Killing this Instance may cause data corruption and should be avoided if possible -instance.kill=Kill \ No newline at end of file +instance.kill=Kill +menu.file.new.url=From URL +menu.file.new.url.details=Select the URL to import from +menu.file.new.url.error=Could not import Instance +menu.file.new.file=From File +menu.file.new.file.error=Could not import Instance +menu.file.new.new=Create \ No newline at end of file diff --git a/launcher-gtk/src/main/resources/inceptum_de.properties b/launcher-gtk/src/main/resources/inceptum_de.properties index 41fc441..726b6ef 100644 --- a/launcher-gtk/src/main/resources/inceptum_de.properties +++ b/launcher-gtk/src/main/resources/inceptum_de.properties @@ -103,4 +103,10 @@ instance.copy.fail=Konnte die Instanz nicht kopieren instance.kill.fail=Ausführung konnte nicht abgebrochen werden instance.kill.prompt=Sind Sie sicher? instance.kill.details=Das vorzeitige Beenden von Instanzen kann zu Datenverlust führen und sollte vermieden werden -instance.kill=Beenden \ No newline at end of file +instance.kill=Beenden +menu.file.new.url=Von einem URL +menu.file.new.url.details=Wähle den URL, von dem die Instanz importiert werden soll +menu.file.new.url.error=Konnte Instanz nicht importieren +menu.file.new.file=Aus einer Datei +menu.file.new.file.error=Konnte Instanz nicht importieren +menu.file.new.new=Erstellen \ No newline at end of file diff --git a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/GuiUtil.java b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/GuiUtil.java index 8d9d7ac..a40f8e8 100644 --- a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/GuiUtil.java +++ b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/GuiUtil.java @@ -22,10 +22,10 @@ public class GuiUtil { try { List paths = JFiles.list(MetaHolder.INSTANCE_DIR); ProcessState state = Steps.createProcessState().extend(paths.size()); - GuiMain.open(new ProcessStateWatcherWindow("Reloading data", "Could not reload resources", state, cToken -> { + GuiMain.open(new ProcessStateWatcherWindow("Reloading data", "Could not reload resources", state, () -> { InstanceList.reset(); InstanceList.forEach(instance -> Steps.reDownload(instance, state)); - }, null)); + })); } catch (IOException e) { LauncherEnv.showError("Could not reload", e); } @@ -33,27 +33,28 @@ public class GuiUtil { public static void reload(Instance instance) { ProcessState state = Steps.createProcessState(); - GuiMain.open(new ProcessStateWatcherWindow("Reloading data", "Could not reload resources", state, cToken -> { + GuiMain.open(new ProcessStateWatcherWindow("Reloading data", "Could not reload resources", state, () -> { Steps.reDownload(instance, state); - }, null)); + })); } public static void createInstance(SetupStepInfo state) { ProcessState pState = Steps.createProcessState(); pState.updateStep("Starting install process"); - GuiMain.open(new ProcessStateWatcherWindow("Creating Instance", "Could not create instance", pState, cToken -> { + GuiMain.open(new ProcessStateWatcherWindow("Creating Instance", "Could not create instance", pState, () -> { for (Step step : Steps.STEPS) { - if (cToken.get()) return; + if (state.isCancelled) { + try { + JFiles.deleteRecursive(MetaHolder.INSTANCE_DIR.resolve(state.name)); + } catch (IOException e) { + Utils.LOGGER.error("Could not delete instance dir", e); + } + return; + } pState.incrementStep(step.name); - step.execute(state, cToken); + step.execute(state); } LauncherEnv.showInfo("The instance was successfully created. You can now launch it using the main menu", "Successfully installed"); - }, () -> { - try { - JFiles.deleteRecursive(MetaHolder.INSTANCE_DIR.resolve(state.name)); - } catch (IOException e) { - Utils.LOGGER.error("Could not delete instance dir", e); - } })); } } diff --git a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/NewInstanceWindow.java b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/NewInstanceWindow.java index c85917a..9cb3bc5 100644 --- a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/NewInstanceWindow.java +++ b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/NewInstanceWindow.java @@ -51,12 +51,13 @@ public class NewInstanceWindow extends Window { List packs = GuiMain.openFileDialog("Import Pack", null, new String[]{"*.zip", "*.mrpack"}, "Modpack", true); if (!packs.isEmpty()) { ProcessState state = new ProcessState(Importers.MAX_STEPS * packs.size(), "Initializing"); - GuiMain.open(new ProcessStateWatcherWindow("Importing", "Could not import packs", state, cToken -> { + GuiMain.open(new ProcessStateWatcherWindow("Importing", "Could not import packs", state, () -> { for (Path pack : packs) { - Path imported = Importers.importPack(pack, state); - LauncherEnv.showInfo(pack.fileName + " has been successfully imported as " + imported.fileName, "Imported pack"); + if (state.isCancelled) return; + Path imported = Importers.importPack(pack, state).path(); + if (!state.isCancelled) LauncherEnv.showInfo(pack.fileName + " has been successfully imported as " + imported.fileName, "Imported pack"); } - }, null)); + })); } } ImGui.endTabItem(); diff --git a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/dialog/ProcessStateWatcherWindow.java b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/dialog/ProcessStateWatcherWindow.java index aa62fb4..b897461 100644 --- a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/dialog/ProcessStateWatcherWindow.java +++ b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/dialog/ProcessStateWatcherWindow.java @@ -1,30 +1,26 @@ package io.gitlab.jfronny.inceptum.imgui.window.dialog; import imgui.ImGui; -import io.gitlab.jfronny.commons.throwable.ThrowingConsumer; +import io.gitlab.jfronny.commons.throwable.ThrowingRunnable; import io.gitlab.jfronny.inceptum.imgui.window.Window; import io.gitlab.jfronny.inceptum.launcher.LauncherEnv; import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; -import org.jetbrains.annotations.Nullable; - -import java.util.concurrent.atomic.AtomicBoolean; public class ProcessStateWatcherWindow extends Window { private final ProcessState state; - private final Runnable cancel; - private final AtomicBoolean canceled = new AtomicBoolean(false); + private boolean finished; - public ProcessStateWatcherWindow(String title, String errorMessage, ProcessState state, ThrowingConsumer executor, @Nullable Runnable cancel) { + public ProcessStateWatcherWindow(String title, String errorMessage, ProcessState state, ThrowingRunnable executor) { super(title); this.state = state; - this.cancel = cancel; new Thread(() -> { try { - executor.accept(canceled); + executor.run(); } catch (Throwable e) { - canceled.set(true); + state.cancel(); LauncherEnv.showError(errorMessage, e); } finally { + finished = true; close(); } }).start(); @@ -38,8 +34,8 @@ public class ProcessStateWatcherWindow extends Window { public void draw() { ImGui.progressBar(state.progress); ImGui.textUnformatted(state.currentStep); - if (cancel != null && ImGui.button("Cancel")) { - canceled.set(true); + if (ImGui.button("Cancel")) { + state.cancel(); close(); } } @@ -47,7 +43,7 @@ public class ProcessStateWatcherWindow extends Window { @Override public void close() { super.close(); - if (canceled.get() && cancel != null) cancel.run(); + if (!finished) state.cancel(); } @Override diff --git a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/ExportTab.java b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/ExportTab.java index e477b56..fce5b4b 100644 --- a/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/ExportTab.java +++ b/launcher-imgui/src/main/java/io/gitlab/jfronny/inceptum/imgui/window/edit/ExportTab.java @@ -39,10 +39,10 @@ public class ExportTab extends Tab { Path exportPath = GuiMain.saveFileDialog("Export " + exporter.name + " Pack", defaultName, new String[]{filter}, exporter.name + " packs (" + filter + ")"); if (exportPath != null) { ProcessState state = new ProcessState(Exporters.STEP_COUNT, "Initializing..."); - GuiMain.open(new ProcessStateWatcherWindow("Exporting", "Could not export pack", state, cToken -> { + GuiMain.open(new ProcessStateWatcherWindow("Exporting", "Could not export pack", state, () -> { exporter.generate(state, window.instance, exportPath); - LauncherEnv.showInfo(window.instance.name + " has been successfully exported to " + exportPath, "Successfully exported"); - }, null)); + if (!state.isCancelled) LauncherEnv.showInfo(window.instance.name + " has been successfully exported to " + exportPath, "Successfully exported"); + })); } } } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importer.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importer.java index d776670..d15868c 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importer.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importer.java @@ -23,7 +23,7 @@ public abstract class Importer { private final String manifestFile; private final ThrowingFunction manifestClass; - public Importer(String name, String manifestFile, ThrowingFunction manifestClass) { + protected Importer(String name, String manifestFile, ThrowingFunction manifestClass) { this.name = name; this.manifestFile = manifestFile; this.manifestClass = manifestClass; @@ -49,7 +49,8 @@ public abstract class Importer { } } - public Path importPack(Path sourceRoot, ProcessState state) throws IOException { + public Instance importPack(Path sourceRoot, ProcessState state) throws IOException { + if (state.isCancelled) return null; state.incrementStep("Generating skeleton"); T manifest = manifestClass.apply(sourceRoot.resolve(manifestFile)); IntermediaryManifest man = validateManifest(manifest, sourceRoot); @@ -62,9 +63,17 @@ public abstract class Importer { meta.gameVersion = createVersionString(man.gameVersion, man.fabricVersion); GC_InstanceMeta.write(meta, iDir.resolve(Instance.CONFIG_NAME)); + if (state.isCancelled) { + JFiles.deleteRecursive(iDir); + return null; + } state.incrementStep("Downloading mods"); downloadMods(manifest, iDir, state); + if (state.isCancelled) { + JFiles.deleteRecursive(iDir); + return null; + } state.incrementStep("Adding overrides"); if (man.overridesPath() != null) { Path overridesPath = sourceRoot.resolve(man.overridesPath); @@ -72,9 +81,18 @@ public abstract class Importer { JFiles.copyRecursive(path, iDir.resolve(path.fileName.toString())); }); } + if (state.isCancelled) { + JFiles.deleteRecursive(iDir); + return null; + } Instance.setSetupLock(iDir, false); - Steps.reDownload(InstanceList.read(iDir), state); - return iDir; + Instance instance = InstanceList.read(iDir); + Steps.reDownload(instance, state); + if (state.isCancelled) { + JFiles.deleteRecursive(iDir); + return null; + } + return instance; } public String getName() { diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java index 8ed6c57..3812a42 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/importer/Importers.java @@ -1,14 +1,18 @@ package io.gitlab.jfronny.inceptum.launcher.system.importer; +import io.gitlab.jfronny.inceptum.common.Net; import io.gitlab.jfronny.inceptum.common.Utils; +import io.gitlab.jfronny.inceptum.launcher.api.CurseforgeApi; +import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance; import io.gitlab.jfronny.inceptum.launcher.system.setup.Steps; import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; import java.io.IOException; import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.Path; +import java.nio.file.*; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class Importers { public static final int MAX_STEPS = Steps.STEPS.size() + 3; @@ -17,7 +21,7 @@ public class Importers { public static final MultiMCImporter MULTI_MC = new MultiMCImporter(); public static final List> IMPORTERS = List.of(CURSE_FORGE, MODRINTH, MULTI_MC); - public static Path importPack(Path zipPath, ProcessState state) throws IOException { + public static Instance importPack(Path zipPath, ProcessState state) throws IOException { try (FileSystem fs = Utils.openZipFile(zipPath, false)) { for (Importer importer : IMPORTERS) { if (importer.canImport(fs.getPath("."))) { @@ -29,4 +33,23 @@ public class Importers { } throw new IOException("Could not import pack: unsupported format"); } + + private static final Pattern CURSEFORGE_URL = Pattern.compile("curseforge://install\\?addonId=(\\d+)&fileId=(\\d+)"); + + public static Instance importPack(String url, ProcessState state) throws IOException { + Path tmp = Files.createTempFile("inceptum", url.endsWith(".mrpack") ? ".mrpack" : ".zip"); + try { + state.updateStep("Downloading Pack"); + Matcher m = CURSEFORGE_URL.matcher(url); + if (m.matches()) { + url = CurseforgeApi.getFile(Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2))).downloadUrl; + } + Net.downloadFile(url, tmp); + return importPack(tmp, state); + } catch (URISyntaxException e) { + throw new IOException("Invalid URL", e); + } finally { + Files.deleteIfExists(tmp); + } + } } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java index a22c979..a6f3586 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/launch/InstanceLauncher.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.*; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; public class InstanceLauncher { @@ -65,7 +64,7 @@ public class InstanceLauncher { versionInfo = FabricMetaApi.addFabric(versionInfo, instance.loaderVersion, launchType.fabricMetaType); } // Ensure libs/assets are present - DownloadLibrariesStep.execute(versionInfo, new AtomicBoolean(false), new ProcessState()); + DownloadLibrariesStep.execute(versionInfo, new ProcessState()); // Prepare arguments List args = new LinkedList<>(); // JVM path diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/SetupStepInfo.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/SetupStepInfo.java index 7957c8b..cf6ef6e 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/SetupStepInfo.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/SetupStepInfo.java @@ -11,4 +11,8 @@ public record SetupStepInfo(VersionInfo version, public void setState(String state) { currentState.updateStep(state); } + + public boolean isCancelled() { + return currentState.isCancelled; + } } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Step.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Step.java index 7f25cd8..6c4a2b2 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Step.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Step.java @@ -1,9 +1,8 @@ package io.gitlab.jfronny.inceptum.launcher.system.setup; import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; public interface Step { - void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException; + void execute(SetupStepInfo info) throws IOException; String getName(); } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Steps.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Steps.java index 5eee9fa..07e97fc 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Steps.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/Steps.java @@ -11,7 +11,6 @@ import io.gitlab.jfronny.inceptum.launcher.util.ProcessState; import java.io.IOException; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; public class Steps { public static Set STEPS = new LinkedHashSet<>(List.of( @@ -29,10 +28,6 @@ public class Steps { } public static void reDownload(Instance instance, ProcessState state) throws IOException { - reDownload(instance, state, new AtomicBoolean(false)); - } - - public static void reDownload(Instance instance, ProcessState state, AtomicBoolean cancel) throws IOException { if (instance.isLocked) return; boolean found = false; for (VersionsListInfo version : McApi.getVersions().versions) { @@ -47,8 +42,8 @@ public class Steps { SetupStepInfo info = new SetupStepInfo(vi, li, instance.name, state); for (Step step : Steps.STEPS) { state.incrementStep(step.name); - step.execute(info, cancel); - if (cancel.get()) return; + step.execute(info); + if (state.isCancelled) return; } } } diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadAssetsStep.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadAssetsStep.java index 541b2ac..a759a9f 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadAssetsStep.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadAssetsStep.java @@ -2,24 +2,20 @@ package io.gitlab.jfronny.inceptum.launcher.system.setup.steps; import io.gitlab.jfronny.inceptum.common.MetaHolder; import io.gitlab.jfronny.inceptum.launcher.api.McApi; -import io.gitlab.jfronny.inceptum.launcher.model.mojang.AssetIndex; import io.gitlab.jfronny.inceptum.launcher.system.setup.SetupStepInfo; import io.gitlab.jfronny.inceptum.launcher.system.setup.Step; import java.io.IOException; -import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; public class DownloadAssetsStep implements Step { @Override - public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException { + public void execute(SetupStepInfo info) throws IOException { Path o = MetaHolder.ASSETS_DIR.resolve("objects"); try { for (var entry : McApi.getAssetIndex(info.version).objects) { - if (stopThread.get()) return; + if (info.isCancelled) return; Path fPath = o.resolve(entry.value.hash.substring(0, 2)); if (!Files.exists(fPath)) Files.createDirectories(fPath); fPath = fPath.resolve(entry.value.hash); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadClientStep.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadClientStep.java index 98a4c0a..2a7481a 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadClientStep.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadClientStep.java @@ -10,11 +10,10 @@ import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.*; -import java.util.concurrent.atomic.AtomicBoolean; public class DownloadClientStep implements Step { @Override - public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException { + public void execute(SetupStepInfo info) throws IOException { Path clientPath = MetaHolder.LIBRARIES_DIR.resolve("net/minecraft/client"); Path serverPath = MetaHolder.LIBRARIES_DIR.resolve("net/minecraft/server"); if (!Files.exists(clientPath)) Files.createDirectories(clientPath); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadJavaStep.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadJavaStep.java index a131701..22cb9d3 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadJavaStep.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadJavaStep.java @@ -11,12 +11,10 @@ import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; public class DownloadJavaStep implements Step { @Override - public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException { + public void execute(SetupStepInfo info) throws IOException { VersionInfo.JavaVersion ver = info.version.javaVersion; Path jvmDir = MetaHolder.NATIVES_DIR.resolve(ver.component).resolve(Integer.toString(ver.majorVersion)); if (Files.exists(jvmDir)) return; diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadLibrariesStep.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadLibrariesStep.java index a3d0875..a63e63e 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadLibrariesStep.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/DownloadLibrariesStep.java @@ -15,12 +15,11 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.*; -import java.util.concurrent.atomic.AtomicBoolean; public class DownloadLibrariesStep implements Step { @Override - public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException { - execute(info.version, stopThread, info.currentState); + public void execute(SetupStepInfo info) throws IOException { + execute(info.version, info.currentState); } @Override @@ -28,9 +27,9 @@ public class DownloadLibrariesStep implements Step { return "Downloading Libraries"; } - public static void execute(VersionInfo version, AtomicBoolean stopThread, ProcessState currentState) throws IOException { + public static void execute(VersionInfo version, ProcessState currentState) throws IOException { for (ArtifactInfo artifact : VersionInfoLibraryResolver.getRelevant(version)) { - if (stopThread.get()) return; + if (currentState.isCancelled) return; Path path = MetaHolder.LIBRARIES_DIR.resolve(artifact.path); if (!Files.exists(path)) { currentState.updateStep("Downloading library: " + artifact.path); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/RunMdsStep.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/RunMdsStep.java index 0123f69..32d43b6 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/RunMdsStep.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/RunMdsStep.java @@ -9,11 +9,10 @@ import io.gitlab.jfronny.inceptum.launcher.system.setup.Step; import java.io.IOException; import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicBoolean; public class RunMdsStep implements Step { @Override - public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException { + public void execute(SetupStepInfo info) throws IOException { info.setState("Running MDS"); Path instance = MetaHolder.INSTANCE_DIR.resolve(info.name); ModsDirScanner.get(instance.resolve("mods"), GC_InstanceMeta.read(instance.resolve(Instance.CONFIG_NAME))) diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/SetupDirsStep.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/SetupDirsStep.java index 79b33ed..3cd739e 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/SetupDirsStep.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/SetupDirsStep.java @@ -10,11 +10,10 @@ import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicBoolean; public class SetupDirsStep implements Step { @Override - public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException { + public void execute(SetupStepInfo info) throws IOException { info.setState("Setting up instance dirs"); Path iDir = MetaHolder.INSTANCE_DIR.resolve(info.name); Instance.setSetupLock(iDir, true); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/WriteMetadataStep.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/WriteMetadataStep.java index f0194f0..03a0b3a 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/WriteMetadataStep.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/system/setup/steps/WriteMetadataStep.java @@ -10,11 +10,10 @@ import io.gitlab.jfronny.inceptum.launcher.system.setup.Step; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicBoolean; public class WriteMetadataStep implements Step { @Override - public void execute(SetupStepInfo info, AtomicBoolean stopThread) throws IOException { + public void execute(SetupStepInfo info) throws IOException { info.setState("Writing metadata"); Path instance = MetaHolder.INSTANCE_DIR.resolve(info.name); Path metaPath = instance.resolve(Instance.CONFIG_NAME); diff --git a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/ProcessState.java b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/ProcessState.java index bce2cdc..b21f207 100644 --- a/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/ProcessState.java +++ b/launcher/src/main/java/io/gitlab/jfronny/inceptum/launcher/util/ProcessState.java @@ -6,6 +6,7 @@ public class ProcessState { private final int maxSteps; private int stepIndex; private String stepDescription; + private boolean isCancelled = false; public ProcessState() { this(0, ""); @@ -43,4 +44,12 @@ public class ProcessState { public float getProgress() { return ((float) stepIndex) / maxSteps; } + + public boolean isCancelled() { + return isCancelled; + } + + public void cancel() { + isCancelled = true; + } }