diff --git a/src/main/java/link/infra/packwiz/installer/CLIHandler.java b/src/main/java/link/infra/packwiz/installer/CLIHandler.java index 3710eb8..0f0fbae 100644 --- a/src/main/java/link/infra/packwiz/installer/CLIHandler.java +++ b/src/main/java/link/infra/packwiz/installer/CLIHandler.java @@ -9,5 +9,24 @@ public class CLIHandler implements IUserInterface { @Override 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(); + } } diff --git a/src/main/java/link/infra/packwiz/installer/IUserInterface.java b/src/main/java/link/infra/packwiz/installer/IUserInterface.java index bd86f05..e9d30a7 100644 --- a/src/main/java/link/infra/packwiz/installer/IUserInterface.java +++ b/src/main/java/link/infra/packwiz/installer/IUserInterface.java @@ -6,11 +6,18 @@ public interface IUserInterface { public void handleException(Exception e); + /** + * This might not exit straight away, return after calling this! + */ public default void handleExceptionAndExit(Exception e) { handleException(e); System.exit(1); }; public default void setTitle(String title) {}; + + public void submitProgress(InstallProgress progress); + + public void executeManager(Runnable task); } diff --git a/src/main/java/link/infra/packwiz/installer/InstallProgress.java b/src/main/java/link/infra/packwiz/installer/InstallProgress.java new file mode 100644 index 0000000..064f367 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/InstallProgress.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/InstallWindow.java b/src/main/java/link/infra/packwiz/installer/InstallWindow.java index 5138209..07d1ed0 100644 --- a/src/main/java/link/infra/packwiz/installer/InstallWindow.java +++ b/src/main/java/link/infra/packwiz/installer/InstallWindow.java @@ -5,6 +5,10 @@ import java.awt.Component; import java.awt.EventQueue; import java.awt.GridBagConstraints; 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.JFrame; @@ -14,13 +18,16 @@ import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.UIManager; import javax.swing.border.EmptyBorder; -import java.awt.event.ActionListener; -import java.awt.event.ActionEvent; public class InstallWindow implements IUserInterface { private JFrame frmPackwizlauncher; + private JLabel lblProgresslabel; + private JProgressBar progressBar; + private String title = "Updating modpack..."; + private SwingWorkerButWithPublicPublish worker; + private AtomicBoolean aboutToCrash = new AtomicBoolean(); @Override public void show() { @@ -52,11 +59,11 @@ public class InstallWindow implements IUserInterface { frmPackwizlauncher.getContentPane().add(panel, BorderLayout.CENTER); panel.setLayout(new BorderLayout(0, 0)); - JProgressBar progressBar = new JProgressBar(); - progressBar.setValue(50); + progressBar = new JProgressBar(); + progressBar.setIndeterminate(true); panel.add(progressBar, BorderLayout.CENTER); - JLabel lblProgresslabel = new JLabel("Loading..."); + lblProgresslabel = new JLabel("Loading..."); panel.add(lblProgresslabel, BorderLayout.SOUTH); JPanel panel_1 = new JPanel(); @@ -75,8 +82,13 @@ public class InstallWindow implements IUserInterface { JButton btnCancel = new JButton("Cancel"); btnCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { - //updateManager.cleanup(); + 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.setAlignmentX(Component.CENTER_ALIGNMENT); @@ -88,7 +100,23 @@ public class InstallWindow implements IUserInterface { @Override 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 @@ -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() { + + @Override + protected Void doInBackground() throws Exception { + task.run(); + return null; + } + + @Override + protected void process(List 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(); + } + }); + } + } diff --git a/src/main/java/link/infra/packwiz/installer/Main.java b/src/main/java/link/infra/packwiz/installer/Main.java index 6cd8099..8d8b681 100644 --- a/src/main/java/link/infra/packwiz/installer/Main.java +++ b/src/main/java/link/infra/packwiz/installer/Main.java @@ -1,5 +1,10 @@ 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.UIManager; @@ -10,14 +15,39 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; public class Main { - + // Actual main() is in RequiresBootstrap! - + 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(); addNonBootstrapOptions(options); addBootstrapOptions(options); - + CommandLineParser parser = new DefaultParser(); CommandLine cmd = null; try { @@ -32,38 +62,65 @@ public class Main { JOptionPane.showMessageDialog(null, e.getMessage(), "packwiz-installer", JOptionPane.ERROR_MESSAGE); System.exit(1); } - + IUserInterface ui; - if (cmd.hasOption("no-gui")) { + // if "headless", GUI creation will fail anyway! + if (cmd.hasOption("no-gui") || GraphicsEnvironment.isHeadless()) { ui = new CLIHandler(); } else { ui = new InstallWindow(); } - + String[] unparsedArgs = cmd.getArgs(); if (unparsedArgs.length > 1) { ui.handleExceptionAndExit(new RuntimeException("Too many arguments specified!")); + return; } else if (unparsedArgs.length < 1) { ui.handleExceptionAndExit(new RuntimeException("URI to install from must be specified!")); + return; } - + String title = cmd.getOptionValue("title"); if (title != null) { ui.setTitle(title); } - - String side = cmd.getOptionValue("side"); - if (side == null) { - side = "client"; - } 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 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"); } @@ -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-no-update", false, "Don't update packwiz-installer"); 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! } diff --git a/src/main/java/link/infra/packwiz/installer/SwingWorkerButWithPublicPublish.java b/src/main/java/link/infra/packwiz/installer/SwingWorkerButWithPublicPublish.java new file mode 100644 index 0000000..44361dd --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/SwingWorkerButWithPublicPublish.java @@ -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 extends SwingWorker { + @SafeVarargs + public final void publishPublic(V... chunks) { + publish(chunks); + } +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/UpdateManager.java b/src/main/java/link/infra/packwiz/installer/UpdateManager.java index a1344f1..446c266 100644 --- a/src/main/java/link/infra/packwiz/installer/UpdateManager.java +++ b/src/main/java/link/infra/packwiz/installer/UpdateManager.java @@ -3,21 +3,86 @@ package link.infra.packwiz.installer; import java.net.URI; public class UpdateManager { - + public final Options opts; public final IUserInterface ui; - + public static class Options { public URI downloadURI; 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) { this.opts = opts; 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 + } } }