Implement optional mods and cancel buttons

This commit is contained in:
comp500 2019-08-11 17:49:56 +01:00
parent 5a54a90f59
commit 7946377159
8 changed files with 182 additions and 36 deletions

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -12,7 +12,7 @@ public class CLIHandler implements IUserInterface {
}
@Override
public void show() {}
public void show(InputStateHandler h) {}
@Override
public void submitProgress(InstallProgress progress) {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;