mirror of
https://github.com/packwiz/packwiz-installer.git
synced 2025-04-19 21:16:30 +02:00
Implement optional mods and cancel buttons
This commit is contained in:
parent
5a54a90f59
commit
7946377159
@ -3,6 +3,7 @@ package link.infra.packwiz.installer;
|
||||
import link.infra.packwiz.installer.metadata.SpaceSafeURI;
|
||||
import link.infra.packwiz.installer.ui.CLIHandler;
|
||||
import link.infra.packwiz.installer.ui.IUserInterface;
|
||||
import link.infra.packwiz.installer.ui.InputStateHandler;
|
||||
import link.infra.packwiz.installer.ui.InstallWindow;
|
||||
import org.apache.commons.cli.*;
|
||||
|
||||
@ -10,10 +11,11 @@ import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Main {
|
||||
|
||||
// Actual main() is in RequiresBootstrap!
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Main(String[] args) {
|
||||
// Big overarching try/catch just in case everything breaks
|
||||
try {
|
||||
@ -79,7 +81,8 @@ public class Main {
|
||||
ui.setTitle(title);
|
||||
}
|
||||
|
||||
ui.show();
|
||||
InputStateHandler inputStateHandler = new InputStateHandler();
|
||||
ui.show(inputStateHandler);
|
||||
|
||||
UpdateManager.Options uOptions = new UpdateManager.Options();
|
||||
|
||||
@ -111,7 +114,7 @@ public class Main {
|
||||
try {
|
||||
ui.executeManager(() -> {
|
||||
try {
|
||||
new UpdateManager(uOptions, ui);
|
||||
new UpdateManager(uOptions, ui, inputStateHandler);
|
||||
} catch (Exception e) {
|
||||
// TODO: better error message?
|
||||
ui.handleExceptionAndExit(e);
|
||||
|
@ -16,6 +16,7 @@ import link.infra.packwiz.installer.metadata.hash.HashUtils;
|
||||
import link.infra.packwiz.installer.request.HandlerManager;
|
||||
import link.infra.packwiz.installer.ui.IExceptionDetails;
|
||||
import link.infra.packwiz.installer.ui.IUserInterface;
|
||||
import link.infra.packwiz.installer.ui.InputStateHandler;
|
||||
import link.infra.packwiz.installer.ui.InstallProgress;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
@ -33,6 +34,7 @@ public class UpdateManager {
|
||||
public final IUserInterface ui;
|
||||
private boolean cancelled;
|
||||
private boolean cancelledStartGame = false;
|
||||
private InputStateHandler stateHandler;
|
||||
|
||||
public static class Options {
|
||||
SpaceSafeURI downloadURI = null;
|
||||
@ -92,9 +94,10 @@ public class UpdateManager {
|
||||
}
|
||||
}
|
||||
|
||||
UpdateManager(Options opts, IUserInterface ui) {
|
||||
UpdateManager(Options opts, IUserInterface ui, InputStateHandler inputStateHandler) {
|
||||
this.opts = opts;
|
||||
this.ui = ui;
|
||||
this.stateHandler = inputStateHandler;
|
||||
this.start();
|
||||
}
|
||||
|
||||
@ -114,6 +117,11 @@ public class UpdateManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
handleCancellation();
|
||||
}
|
||||
|
||||
ui.submitProgress(new InstallProgress("Loading pack file..."));
|
||||
GeneralHashingSource packFileSource;
|
||||
try {
|
||||
@ -133,6 +141,11 @@ public class UpdateManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
handleCancellation();
|
||||
}
|
||||
|
||||
ui.submitProgress(new InstallProgress("Checking local files..."));
|
||||
|
||||
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked
|
||||
@ -162,11 +175,18 @@ public class UpdateManager {
|
||||
if (manifest.packFileHash != null && packFileSource.hashIsEqual(manifest.packFileHash) && invalidatedUris.isEmpty()) {
|
||||
System.out.println("Modpack is already up to date!");
|
||||
// todo: --force?
|
||||
return;
|
||||
if (!stateHandler.getOptionsButton()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Modpack name: " + pf.name);
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
handleCancellation();
|
||||
}
|
||||
|
||||
try {
|
||||
// This is badly written, I'll probably heavily refactor it at some point
|
||||
processIndex(HandlerManager.getNewLoc(opts.downloadURI, pf.index.file),
|
||||
@ -175,15 +195,7 @@ public class UpdateManager {
|
||||
ui.handleExceptionAndExit(e1);
|
||||
}
|
||||
|
||||
if (cancelled) {
|
||||
System.out.println("Update cancelled by user!");
|
||||
System.exit(1);
|
||||
return;
|
||||
} else if (cancelledStartGame) {
|
||||
System.out.println("Update cancelled by user! Continuing to start game...");
|
||||
System.exit(0);
|
||||
return;
|
||||
}
|
||||
handleCancellation();
|
||||
|
||||
// TODO: update MMC params, java args etc
|
||||
|
||||
@ -205,7 +217,9 @@ public class UpdateManager {
|
||||
private void processIndex(SpaceSafeURI indexUri, Hash indexHash, String hashFormat, ManifestFile manifest, List<SpaceSafeURI> invalidatedUris) {
|
||||
if (manifest.indexFileHash != null && manifest.indexFileHash.equals(indexHash) && invalidatedUris.isEmpty()) {
|
||||
System.out.println("Modpack files are already up to date!");
|
||||
return;
|
||||
if (!stateHandler.getOptionsButton()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
manifest.indexFileHash = indexHash;
|
||||
|
||||
@ -233,6 +247,11 @@ public class UpdateManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
if (manifest.cachedFiles == null) {
|
||||
manifest.cachedFiles = new HashMap<>();
|
||||
}
|
||||
@ -269,13 +288,21 @@ public class UpdateManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
ui.submitProgress(new InstallProgress("Comparing new files..."));
|
||||
|
||||
// TODO: progress bar, parallelify
|
||||
// TODO: progress bar?
|
||||
List<DownloadTask> tasks = DownloadTask.createTasksFromIndex(indexFile, indexFile.hashFormat, opts.side);
|
||||
// If the side changes, invalidate EVERYTHING just in case
|
||||
// Might not be needed, but done just to be safe
|
||||
boolean invalidateAll = !opts.side.equals(manifest.cachedSide);
|
||||
if (invalidateAll) {
|
||||
System.out.println("Side changed, invalidating all mods");
|
||||
}
|
||||
tasks.forEach(f -> {
|
||||
// TODO: should linkedfile be checked as well? should this be done in the download section?
|
||||
if (invalidateAll) {
|
||||
@ -291,7 +318,14 @@ public class UpdateManager {
|
||||
// If it is null, the DownloadTask will make a new empty cachedFile
|
||||
f.updateFromCache(file);
|
||||
});
|
||||
tasks.forEach(f -> f.downloadMetadata(indexFile, indexUri));
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's hope downloadMetadata is a pure function!!!
|
||||
tasks.parallelStream().forEach(f -> f.downloadMetadata(indexFile, indexUri));
|
||||
|
||||
List<IExceptionDetails> failedTasks = tasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList());
|
||||
if (failedTasks.size() > 0) {
|
||||
@ -315,10 +349,15 @@ public class UpdateManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
showCancellationDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
List<DownloadTask> nonFailedFirstTasks = tasks.stream().filter(t -> t.getException() == null).collect(Collectors.toList());
|
||||
List<DownloadTask> optionTasks = nonFailedFirstTasks.stream().filter(DownloadTask::correctSide).filter(DownloadTask::isOptional).collect(Collectors.toList());
|
||||
// If options changed, present all options again
|
||||
if (optionTasks.stream().anyMatch(DownloadTask::isNewOptional)) {
|
||||
if (stateHandler.getOptionsButton() || optionTasks.stream().anyMatch(DownloadTask::isNewOptional)) {
|
||||
// new ArrayList is requires so it's an IOptionDetails rather than a DownloadTask list
|
||||
Future<Boolean> cancelledResult = ui.showOptions(new ArrayList<>(optionTasks));
|
||||
try {
|
||||
@ -332,6 +371,7 @@ public class UpdateManager {
|
||||
ui.handleExceptionAndExit(e);
|
||||
}
|
||||
}
|
||||
ui.disableOptionsButton();
|
||||
|
||||
// TODO: different thread pool type?
|
||||
ExecutorService threadPool = Executors.newFixedThreadPool(10);
|
||||
@ -355,7 +395,7 @@ public class UpdateManager {
|
||||
if (task.getException() != null) {
|
||||
ManifestFile.File file = task.cachedFile.getRevert();
|
||||
if (file != null) {
|
||||
manifest.cachedFiles.put(task.metadata.file, file);
|
||||
manifest.cachedFiles.putIfAbsent(task.metadata.file, file);
|
||||
}
|
||||
} else {
|
||||
// idiot, if it wasn't there in the first place it won't magically appear there
|
||||
@ -376,6 +416,13 @@ public class UpdateManager {
|
||||
progress = "Failed to download, unknown reason";
|
||||
}
|
||||
ui.submitProgress(new InstallProgress(progress, i + 1, tasks.size()));
|
||||
|
||||
if (stateHandler.getCancelButton()) {
|
||||
// Stop all tasks, don't launch the game (it's in an invalid state!)
|
||||
threadPool.shutdown();
|
||||
cancelled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
List<IExceptionDetails> failedTasks2ElectricBoogaloo = nonFailedFirstTasks.stream().filter(t -> t.getException() != null).collect(Collectors.toList());
|
||||
@ -399,4 +446,34 @@ public class UpdateManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showCancellationDialog() {
|
||||
IExceptionDetails.ExceptionListResult exceptionListResult;
|
||||
try {
|
||||
exceptionListResult = ui.showCancellationDialog().get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
// Interrupted means cancelled???
|
||||
ui.handleExceptionAndExit(e);
|
||||
return;
|
||||
}
|
||||
switch (exceptionListResult) {
|
||||
case CONTINUE:
|
||||
throw new RuntimeException("Continuation not allowed here!");
|
||||
case CANCEL:
|
||||
cancelled = true;
|
||||
return;
|
||||
case IGNORE:
|
||||
cancelledStartGame = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCancellation() {
|
||||
if (cancelled) {
|
||||
System.out.println("Update cancelled by user!");
|
||||
System.exit(1);
|
||||
} else if (cancelledStartGame) {
|
||||
System.out.println("Update cancelled by user! Continuing to start game...");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,6 @@ public class IndexFile {
|
||||
|
||||
public transient ModFile linkedFile;
|
||||
public transient SpaceSafeURI linkedFileURI;
|
||||
public transient boolean optionValue = true;
|
||||
|
||||
public void downloadMeta(IndexFile parentIndexFile, SpaceSafeURI indexUri) throws Exception {
|
||||
if (!metafile) {
|
||||
|
@ -12,7 +12,7 @@ public class CLIHandler implements IUserInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {}
|
||||
public void show(InputStateHandler h) {}
|
||||
|
||||
@Override
|
||||
public void submitProgress(InstallProgress progress) {
|
||||
|
@ -1,11 +1,12 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public interface IUserInterface {
|
||||
|
||||
void show();
|
||||
void show(InputStateHandler handler);
|
||||
|
||||
void handleException(Exception e);
|
||||
|
||||
@ -24,5 +25,14 @@ public interface IUserInterface {
|
||||
Future<Boolean> showOptions(List<IOptionDetails> option);
|
||||
|
||||
Future<IExceptionDetails.ExceptionListResult> showExceptions(List<IExceptionDetails> opts, int numTotal, boolean allowsIgnore);
|
||||
|
||||
default void disableOptionsButton() {}
|
||||
|
||||
// Should not return CONTINUE
|
||||
default Future<IExceptionDetails.ExceptionListResult> showCancellationDialog() {
|
||||
CompletableFuture<IExceptionDetails.ExceptionListResult> future = new CompletableFuture<>();
|
||||
future.complete(IExceptionDetails.ExceptionListResult.CANCEL);
|
||||
return future;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
public class InputStateHandler {
|
||||
private boolean optionsButtonPressed = false;
|
||||
private boolean cancelButtonPressed = false;
|
||||
|
||||
synchronized void pressCancelButton() {
|
||||
this.cancelButtonPressed = true;
|
||||
}
|
||||
|
||||
synchronized void pressOptionsButton() {
|
||||
this.optionsButtonPressed = true;
|
||||
}
|
||||
|
||||
public synchronized boolean getCancelButton() {
|
||||
return cancelButtonPressed;
|
||||
}
|
||||
|
||||
public synchronized boolean getOptionsButton() {
|
||||
return optionsButtonPressed;
|
||||
}
|
||||
}
|
@ -13,18 +13,21 @@ public class InstallWindow implements IUserInterface {
|
||||
private JFrame frmPackwizlauncher;
|
||||
private JLabel lblProgresslabel;
|
||||
private JProgressBar progressBar;
|
||||
private InputStateHandler inputStateHandler;
|
||||
|
||||
private String title = "Updating modpack...";
|
||||
private SwingWorkerButWithPublicPublish<Void, InstallProgress> worker;
|
||||
private AtomicBoolean aboutToCrash = new AtomicBoolean();
|
||||
private JButton btnOptions;
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
public void show(InputStateHandler handler) {
|
||||
this.inputStateHandler = handler;
|
||||
EventQueue.invokeLater(() -> {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
InstallWindow.this.initialize();
|
||||
InstallWindow.this.frmPackwizlauncher.setVisible(true);
|
||||
initialize();
|
||||
frmPackwizlauncher.setVisible(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -60,7 +63,12 @@ public class InstallWindow implements IUserInterface {
|
||||
GridBagLayout gbl_panel_1 = new GridBagLayout();
|
||||
panel_1.setLayout(gbl_panel_1);
|
||||
|
||||
JButton btnOptions = new JButton("Configure...");
|
||||
btnOptions = new JButton("Optional mods...");
|
||||
btnOptions.addActionListener(e -> {
|
||||
btnOptions.setText("Loading...");
|
||||
btnOptions.setEnabled(false);
|
||||
inputStateHandler.pressOptionsButton();
|
||||
});
|
||||
btnOptions.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
GridBagConstraints gbc_btnOptions = new GridBagConstraints();
|
||||
gbc_btnOptions.gridx = 0;
|
||||
@ -68,14 +76,9 @@ public class InstallWindow implements IUserInterface {
|
||||
panel_1.add(btnOptions, gbc_btnOptions);
|
||||
|
||||
JButton btnCancel = new JButton("Cancel");
|
||||
btnCancel.addActionListener(event -> {
|
||||
if (worker != null) {
|
||||
worker.cancel(true);
|
||||
}
|
||||
frmPackwizlauncher.dispose();
|
||||
// TODO: show window to ask user what to do
|
||||
System.out.println("Update process cancelled by user!");
|
||||
System.exit(1);
|
||||
btnCancel.addActionListener(e -> {
|
||||
btnCancel.setEnabled(false);
|
||||
inputStateHandler.pressCancelButton();
|
||||
});
|
||||
btnCancel.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
GridBagConstraints gbc_btnCancel = new GridBagConstraints();
|
||||
@ -113,12 +116,23 @@ public class InstallWindow implements IUserInterface {
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
if (frmPackwizlauncher != null) {
|
||||
EventQueue.invokeLater(() -> InstallWindow.this.frmPackwizlauncher.setTitle(title));
|
||||
EventQueue.invokeLater(() -> frmPackwizlauncher.setTitle(title));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitProgress(InstallProgress progress) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (progress.hasProgress) {
|
||||
sb.append('(');
|
||||
sb.append(progress.progress);
|
||||
sb.append('/');
|
||||
sb.append(progress.progressTotal);
|
||||
sb.append(") ");
|
||||
}
|
||||
sb.append(progress.message);
|
||||
// TODO: better logging library?
|
||||
System.out.println(sb.toString());
|
||||
if (worker != null) {
|
||||
worker.publishPublic(progress);
|
||||
}
|
||||
@ -190,4 +204,25 @@ public class InstallWindow implements IUserInterface {
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableOptionsButton() {
|
||||
if (btnOptions != null) {
|
||||
btnOptions.setText("Optional mods...");
|
||||
btnOptions.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<IExceptionDetails.ExceptionListResult> showCancellationDialog() {
|
||||
CompletableFuture<IExceptionDetails.ExceptionListResult> future = new CompletableFuture<>();
|
||||
EventQueue.invokeLater(() -> {
|
||||
Object[] buttons = {"Quit", "Ignore"};
|
||||
int result = JOptionPane.showOptionDialog(frmPackwizlauncher,
|
||||
"The installation was cancelled. Would you like to quit the game, or ignore the update and start the game?",
|
||||
"Cancelled installation",
|
||||
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, buttons, buttons[0]);
|
||||
future.complete(result == 0 ? IExceptionDetails.ExceptionListResult.CANCEL : IExceptionDetails.ExceptionListResult.IGNORE);
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package link.infra.packwiz.installer.ui;
|
||||
|
||||
// Serves as a proxy for IOptionDetails, so that setOptionValue isn't called until OK is clicked
|
||||
public class OptionTempHandler implements IOptionDetails {
|
||||
class OptionTempHandler implements IOptionDetails {
|
||||
private final IOptionDetails opt;
|
||||
private boolean tempValue;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user