Better exception handling, progress system

This commit is contained in:
comp500 2019-06-06 23:25:22 +01:00
parent 905630cb2a
commit 3d28f0a674
No known key found for this signature in database
GPG Key ID: 214C822FFEC586B5
7 changed files with 292 additions and 28 deletions

View File

@ -10,4 +10,23 @@ public class CLIHandler implements IUserInterface {
@Override @Override
public void show() {} public void show() {}
@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);
System.out.println(sb.toString());
}
@Override
public void executeManager(Runnable task) {
task.run();
}
} }

View File

@ -6,6 +6,9 @@ public interface IUserInterface {
public void handleException(Exception e); public void handleException(Exception e);
/**
* This might not exit straight away, return after calling this!
*/
public default void handleExceptionAndExit(Exception e) { public default void handleExceptionAndExit(Exception e) {
handleException(e); handleException(e);
System.exit(1); System.exit(1);
@ -13,4 +16,8 @@ public interface IUserInterface {
public default void setTitle(String title) {}; public default void setTitle(String title) {};
public void submitProgress(InstallProgress progress);
public void executeManager(Runnable task);
} }

View File

@ -0,0 +1,22 @@
package link.infra.packwiz.installer;
public class InstallProgress {
public final String message;
public final boolean hasProgress;
public final int progress;
public final int progressTotal;
InstallProgress(String message) {
this.message = message;
hasProgress = false;
progress = 0;
progressTotal = 0;
}
InstallProgress(String message, int progress, int progressTotal) {
this.message = message;
hasProgress = true;
this.progress = progress;
this.progressTotal = progressTotal;
}
}

View File

@ -5,6 +5,10 @@ import java.awt.Component;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.GridBagConstraints; import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JFrame; import javax.swing.JFrame;
@ -14,13 +18,16 @@ import javax.swing.JPanel;
import javax.swing.JProgressBar; import javax.swing.JProgressBar;
import javax.swing.UIManager; import javax.swing.UIManager;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class InstallWindow implements IUserInterface { public class InstallWindow implements IUserInterface {
private JFrame frmPackwizlauncher; private JFrame frmPackwizlauncher;
private JLabel lblProgresslabel;
private JProgressBar progressBar;
private String title = "Updating modpack..."; private String title = "Updating modpack...";
private SwingWorkerButWithPublicPublish<Void, InstallProgress> worker;
private AtomicBoolean aboutToCrash = new AtomicBoolean();
@Override @Override
public void show() { public void show() {
@ -52,11 +59,11 @@ public class InstallWindow implements IUserInterface {
frmPackwizlauncher.getContentPane().add(panel, BorderLayout.CENTER); frmPackwizlauncher.getContentPane().add(panel, BorderLayout.CENTER);
panel.setLayout(new BorderLayout(0, 0)); panel.setLayout(new BorderLayout(0, 0));
JProgressBar progressBar = new JProgressBar(); progressBar = new JProgressBar();
progressBar.setValue(50); progressBar.setIndeterminate(true);
panel.add(progressBar, BorderLayout.CENTER); panel.add(progressBar, BorderLayout.CENTER);
JLabel lblProgresslabel = new JLabel("Loading..."); lblProgresslabel = new JLabel("Loading...");
panel.add(lblProgresslabel, BorderLayout.SOUTH); panel.add(lblProgresslabel, BorderLayout.SOUTH);
JPanel panel_1 = new JPanel(); JPanel panel_1 = new JPanel();
@ -75,8 +82,13 @@ public class InstallWindow implements IUserInterface {
JButton btnCancel = new JButton("Cancel"); JButton btnCancel = new JButton("Cancel");
btnCancel.addActionListener(new ActionListener() { btnCancel.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) { public void actionPerformed(ActionEvent arg0) {
//updateManager.cleanup(); if (worker != null) {
worker.cancel(true);
}
frmPackwizlauncher.dispose(); frmPackwizlauncher.dispose();
// TODO: show window to ask user what to do
System.out.println("Update process cancelled by user!");
System.exit(1);
} }
}); });
btnCancel.setAlignmentX(Component.CENTER_ALIGNMENT); btnCancel.setAlignmentX(Component.CENTER_ALIGNMENT);
@ -88,7 +100,23 @@ public class InstallWindow implements IUserInterface {
@Override @Override
public void handleException(Exception e) { public void handleException(Exception e) {
JOptionPane.showMessageDialog(null, e.getMessage(), title, JOptionPane.ERROR_MESSAGE); EventQueue.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(null, e.getMessage(), title, JOptionPane.ERROR_MESSAGE);
}
});
}
@Override
public void handleExceptionAndExit(Exception e) {
// Used to prevent the done() handler of SwingWorker executing if the invokeLater hasn't happened yet
aboutToCrash.set(true);
EventQueue.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(null, e.getMessage(), title, JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
});
} }
@Override @Override
@ -103,4 +131,57 @@ public class InstallWindow implements IUserInterface {
} }
} }
@Override
public void submitProgress(InstallProgress progress) {
if (worker != null) {
worker.publishPublic(progress);
}
}
@Override
public void executeManager(Runnable task) {
EventQueue.invokeLater(new Runnable() {
public void run() {
worker = new SwingWorkerButWithPublicPublish<Void, InstallProgress>() {
@Override
protected Void doInBackground() throws Exception {
task.run();
return null;
}
@Override
protected void process(List<InstallProgress> chunks) {
// Only process last chunk
if (chunks.size() > 0) {
InstallProgress prog = chunks.get(chunks.size() - 1);
if (prog.hasProgress) {
progressBar.setIndeterminate(false);
progressBar.setValue(prog.progress);
progressBar.setMaximum(prog.progressTotal);
} else {
progressBar.setIndeterminate(true);
progressBar.setValue(0);
}
lblProgresslabel.setText(prog.message);
}
}
@Override
protected void done() {
if (aboutToCrash.get()) {
return;
}
// TODO: a better way to do this?
frmPackwizlauncher.dispose();
System.out.println("Finished successfully!");
System.exit(0);
}
};
worker.execute();
}
});
}
} }

View File

@ -1,5 +1,10 @@
package link.infra.packwiz.installer; package link.infra.packwiz.installer;
import java.awt.EventQueue;
import java.awt.GraphicsEnvironment;
import java.net.URI;
import java.net.URISyntaxException;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.UIManager; import javax.swing.UIManager;
@ -14,6 +19,31 @@ public class Main {
// Actual main() is in RequiresBootstrap! // Actual main() is in RequiresBootstrap!
public Main(String[] args) { public Main(String[] args) {
// Big overarching try/catch just in case everything breaks
try {
this.startup(args);
} catch (Exception e) {
e.printStackTrace();
EventQueue.invokeLater(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(null,
"A fatal error occurred: \n" + e.getClass().getCanonicalName() + ": " + e.getMessage(),
"packwiz-installer", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
});
// In case the eventqueue is broken, exit after 1 minute
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException e1) {
// Good, it was already called?
return;
}
System.exit(1);
}
}
protected void startup(String[] args) {
Options options = new Options(); Options options = new Options();
addNonBootstrapOptions(options); addNonBootstrapOptions(options);
addBootstrapOptions(options); addBootstrapOptions(options);
@ -34,7 +64,8 @@ public class Main {
} }
IUserInterface ui; IUserInterface ui;
if (cmd.hasOption("no-gui")) { // if "headless", GUI creation will fail anyway!
if (cmd.hasOption("no-gui") || GraphicsEnvironment.isHeadless()) {
ui = new CLIHandler(); ui = new CLIHandler();
} else { } else {
ui = new InstallWindow(); ui = new InstallWindow();
@ -43,8 +74,10 @@ public class Main {
String[] unparsedArgs = cmd.getArgs(); String[] unparsedArgs = cmd.getArgs();
if (unparsedArgs.length > 1) { if (unparsedArgs.length > 1) {
ui.handleExceptionAndExit(new RuntimeException("Too many arguments specified!")); ui.handleExceptionAndExit(new RuntimeException("Too many arguments specified!"));
return;
} else if (unparsedArgs.length < 1) { } else if (unparsedArgs.length < 1) {
ui.handleExceptionAndExit(new RuntimeException("URI to install from must be specified!")); ui.handleExceptionAndExit(new RuntimeException("URI to install from must be specified!"));
return;
} }
String title = cmd.getOptionValue("title"); String title = cmd.getOptionValue("title");
@ -52,18 +85,42 @@ public class Main {
ui.setTitle(title); ui.setTitle(title);
} }
String side = cmd.getOptionValue("side");
if (side == null) {
side = "client";
}
ui.show(); ui.show();
UpdateManager.Options uOptions = new UpdateManager.Options();
String side = cmd.getOptionValue("side");
if (side != null) {
uOptions.side = UpdateManager.Options.Side.from(side);
}
try {
uOptions.downloadURI = new URI(unparsedArgs[0]);
} catch (URISyntaxException e) {
// TODO: better error message?
ui.handleExceptionAndExit(e);
return;
}
// Start update process!
// TODO: start in SwingWorker?
try {
ui.executeManager(new Runnable(){
@Override
public void run() {
new UpdateManager(uOptions, ui);
}
});
} catch (Exception e) {
// TODO: better error message?
ui.handleExceptionAndExit(e);
return;
}
} }
// Called by packwiz-installer-bootstrap to set up the help command // Called by packwiz-installer-bootstrap to set up the help command
public static void addNonBootstrapOptions(Options options) { public static void addNonBootstrapOptions(Options options) {
options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)"); // TODO: implement options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)");
options.addOption(null, "title", true, "Title of the installer window"); options.addOption(null, "title", true, "Title of the installer window");
} }
@ -73,7 +130,7 @@ public class Main {
options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories"); options.addOption(null, "bootstrap-update-token", true, "Github API Access Token, for private repositories");
options.addOption(null, "bootstrap-no-update", false, "Don't update packwiz-installer"); options.addOption(null, "bootstrap-no-update", false, "Don't update packwiz-installer");
options.addOption(null, "bootstrap-main-jar", true, "Location of the packwiz-installer JAR file"); options.addOption(null, "bootstrap-main-jar", true, "Location of the packwiz-installer JAR file");
options.addOption("g", "no-gui", false, "Don't display a GUI to show update progress"); // TODO: implement options.addOption("g", "no-gui", false, "Don't display a GUI to show update progress");
options.addOption("h", "help", false, "Display this message"); // Implemented in packwiz-installer-bootstrap! options.addOption("h", "help", false, "Display this message"); // Implemented in packwiz-installer-bootstrap!
} }

View File

@ -0,0 +1,13 @@
package link.infra.packwiz.installer;
import javax.swing.SwingWorker;
// Q: AAA WHAT HAVE YOU DONE THIS IS DISGUSTING
// A: it just makes things easier, so i can easily have one interface for CLI/GUI
// if someone has a better way to do this please PR it
public abstract class SwingWorkerButWithPublicPublish<T,V> extends SwingWorker<T,V> {
@SafeVarargs
public final void publishPublic(V... chunks) {
publish(chunks);
}
}

View File

@ -10,14 +10,79 @@ public class UpdateManager {
public static class Options { public static class Options {
public URI downloadURI; public URI downloadURI;
public String manifestFile = "packwiz.json"; public String manifestFile = "packwiz.json";
public Side side = Side.CLIENT;
public static enum Side {
CLIENT("client"), SERVER("server"), BOTH("both", new Side[] { CLIENT, SERVER });
private final String sideName;
private final Side[] depSides;
Side(String sideName) {
this.sideName = sideName.toLowerCase();
this.depSides = null;
}
Side(String sideName, Side[] depSides) {
this.sideName = sideName.toLowerCase();
this.depSides = depSides;
}
@Override
public String toString() {
return this.sideName;
}
public boolean hasSide(Side tSide) {
if (this.equals(tSide)) {
return true;
}
if (this.depSides != null) {
for (int i = 0; i < this.depSides.length; i++) {
if (this.depSides[i].equals(tSide)) {
return true;
}
}
}
return false;
}
public static Side from(String name) {
String lowerName = name.toLowerCase();
for (Side side : Side.values()) {
if (side.sideName == lowerName) {
return side;
}
}
return null;
}
}
} }
public UpdateManager(Options opts, IUserInterface ui) { public UpdateManager(Options opts, IUserInterface ui) {
this.opts = opts; this.opts = opts;
this.ui = ui; this.ui = ui;
this.start();
} }
public void cleanup() { protected void start() {
ui.submitProgress(new InstallProgress("Loading pack file..."));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Big oof
}
ui.submitProgress(new InstallProgress("Loading metadata", 1, 2));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Big oof
}
ui.submitProgress(new InstallProgress("Loading magic", 2, 2));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Big oof
}
} }
} }