From 7c651fbd73c944f9942119cfc4f8ec6551d17204 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 4 Nov 2016 17:45:51 -0700 Subject: [PATCH] Tidy up new resources stuff a bit. The one major change here is that Getdown.isUpdateAvailable/install() are no longer static. GetdownApp has a start() method that you can call if you want to get the Getdown reference back. You can call isUpdateAvailable() on that. It was super weird that you would call GetdownApp.main() and then just call some static methods after that. --- .../com/threerings/getdown/data/Resource.java | 27 ++- .../com/threerings/getdown/data/SysProps.java | 7 +- .../threerings/getdown/launcher/Getdown.java | 103 +++++---- .../getdown/launcher/GetdownApp.java | 214 +++++++++--------- .../com/threerings/getdown/util/FileUtil.java | 4 +- 5 files changed, 189 insertions(+), 166 deletions(-) diff --git a/src/main/java/com/threerings/getdown/data/Resource.java b/src/main/java/com/threerings/getdown/data/Resource.java index c81583a..928d33e 100644 --- a/src/main/java/com/threerings/getdown/data/Resource.java +++ b/src/main/java/com/threerings/getdown/data/Resource.java @@ -35,7 +35,7 @@ public class Resource _path = path; _remote = remote; _local = local; - _local_new = new File(local.toString() + "_new"); + _localNew = new File(local.toString() + "_new"); String lpath = _local.getPath(); _marker = new File(lpath + "v"); @@ -67,9 +67,12 @@ public class Resource return _local; } + /** + * Returns the location of the to-be-installed new version of this resource. + */ public File getLocalNew () { - return _local_new; + return _localNew; } /** @@ -109,18 +112,14 @@ public class Resource * Computes the MD5 hash of this resource's underlying file. * Note: This is both CPU and I/O intensive. */ - public String computeDigest (MessageDigest md, ProgressObserver obs) - throws IOException + public String computeDigest (MessageDigest md, ProgressObserver obs) throws IOException { - File file; - if (_local.toString().toLowerCase().endsWith(Application.CONFIG_FILE)) - file = _local; - else { - if (_local_new.exists()) - file = _local_new; - else - file = _local; - } + File file; + if (_local.toString().toLowerCase().endsWith(Application.CONFIG_FILE)) { + file = _local; + } else { + file = _localNew.exists() ? _localNew : _local; + } return computeDigest(file, md, obs); } @@ -309,7 +308,7 @@ public class Resource protected String _path; protected URL _remote; - protected File _local, _local_new, _marker, _unpacked; + protected File _local, _localNew, _marker, _unpacked; protected boolean _unpack, _isJar, _isPacked200Jar; /** Used to sort the entries in a jar file. */ diff --git a/src/main/java/com/threerings/getdown/data/SysProps.java b/src/main/java/com/threerings/getdown/data/SysProps.java index 39381b0..b883ba5 100644 --- a/src/main/java/com/threerings/getdown/data/SysProps.java +++ b/src/main/java/com/threerings/getdown/data/SysProps.java @@ -49,8 +49,11 @@ public class SysProps return System.getProperty("silent") != null; } - public static boolean install () { - return System.getProperty("no_install") == null; + /** If true, Getdown does not automatically install updates after downloading them. It waits + * for the application to call {@link Getdown#install}. + * Usage: {@code -Dno_install}. */ + public static boolean noInstall () { + return System.getProperty("no_install") != null; } /** If true, Getdown installs the app without ever bringing up a UI and then launches it. diff --git a/src/main/java/com/threerings/getdown/launcher/Getdown.java b/src/main/java/com/threerings/getdown/launcher/Getdown.java index ef9c280..b625ae1 100644 --- a/src/main/java/com/threerings/getdown/launcher/Getdown.java +++ b/src/main/java/com/threerings/getdown/launcher/Getdown.java @@ -101,11 +101,11 @@ public abstract class Getdown extends Thread // If the silent property exists, install without bringing up any gui. If it equals // launch, start the application after installing. Otherwise, just install and exit. _silent = SysProps.silent(); - _install = SysProps.install(); if (_silent) { _launchInSilent = SysProps.launchInSilent(); } _delay = SysProps.startDelay(); + _noInstall = SysProps.noInstall(); } catch (SecurityException se) { // don't freak out, just assume non-silent and no delay; we're probably already // recovering from a security failure @@ -128,6 +128,39 @@ public abstract class Getdown extends Thread _startup = System.currentTimeMillis(); } + /** + * Returns true if there are pending new resources, waiting to be installed. + */ + public boolean isUpdateAvailable () + { + return _readyToInstall && !_toBeInstalledResouces.isEmpty(); + } + + /** + * Installs the currently pending new resources. + */ + public void install () throws IOException, InterruptedException + { + if (_readyToInstall) { + log.info("Installing downloaded resources:"); + for (Resource resource : _toBeInstalledResouces) { + File source = resource.getLocalNew(), dest = resource.getLocal(); + log.info("- " + source); + if (!FileUtil.renameTo(source, dest)) { + throw new IOException("Failed to rename " + source + " to " + dest); + } + if (Thread.interrupted()) { + throw new InterruptedException("m.applet_stopped"); + } + } + _toBeInstalledResouces.clear(); + _readyToInstall = false; + log.info("Install completed."); + } else { + log.info("Nothing to install."); + } + } + /** * This is used by the applet which always needs a user interface and wants to load it as soon * as possible. @@ -411,9 +444,9 @@ public abstract class Getdown extends Thread // we'll keep track of all the resources we unpack Set unpacked = new HashSet(); - - toBeInstalledResouces = new ArrayList(); - readyToInstall = false; + + _toBeInstalledResouces = new ArrayList(); + _readyToInstall = false; //setStep(Step.START); for (int ii = 0; ii < MAX_LOOPS; ii++) { @@ -447,7 +480,6 @@ public abstract class Getdown extends Thread setStep(Step.VERIFY_RESOURCES); setStatusAsync("m.validating", -1, -1L, false); List failures = _app.verifyResources(_progobs, alreadyValid, unpacked); - addToBeInstalledResources(failures); if (failures == null) { log.info("Resources verified."); @@ -477,9 +509,11 @@ public abstract class Getdown extends Thread } } - readyToInstall = true; - if (_install) - install(); + // assuming we're not doing anything funny, install the update + _readyToInstall = true; + if (!_noInstall) { + install(); + } // Only launch if we aren't in silent mode. Some mystery program starting out // of the blue would be disconcerting. @@ -496,6 +530,13 @@ public abstract class Getdown extends Thread return; } + // we have failures, those will be redownloaded so we note them as to-be-installed + for (Resource r : failures) { + if (!_toBeInstalledResouces.contains(r)) { + _toBeInstalledResouces.add(r); + } + } + try { // if any of our resources have already been marked valid this is not a first // time install and we don't want to enable tracking @@ -509,6 +550,7 @@ public abstract class Getdown extends Thread download(failures); reportTrackingEvent("app_complete", -1); + } finally { _enableTracking = false; } @@ -541,16 +583,7 @@ public abstract class Getdown extends Thread } } - private void addToBeInstalledResources(List resources) { - if (resources == null) - return; - else - for (Resource r : resources) - if (!toBeInstalledResouces.contains(r)) - toBeInstalledResouces.add(r); - } - - // documentation inherited from interface + // documentation inherited from interface public void updateStatus (String message) { setStatusAsync(message, -1, -1L, true); @@ -773,32 +806,7 @@ public abstract class Getdown extends Thread throw new MultipleGetdownRunning(); } } - - public static void install () - throws IOException, InterruptedException - { - if (readyToInstall) { - log.info("Installing downloaded resources:"); - for (Resource resource : toBeInstalledResouces) { - log.info(resource); - if (!FileUtil.renameTo(resource.getLocalNew(), resource.getLocal())) - throw new IOException("Failed to rename(" + resource.getLocalNew() + ", " + resource.getLocal() + ")"); - if (Thread.interrupted()) { - throw new InterruptedException("m.applet_stopped"); - } - } - toBeInstalledResouces.clear(); - readyToInstall = false; - log.info("Install completed."); - } else { - log.info("Nothing to install."); - } - } - - public static boolean isUpdateAvailable() { - return readyToInstall && !toBeInstalledResouces.isEmpty(); - } - + /** * Called to launch the application if everything is determined to be ready to go. */ @@ -1263,10 +1271,13 @@ public abstract class Getdown extends Thread protected boolean _dead; protected boolean _silent; - protected boolean _install; + protected boolean _noInstall; protected boolean _launchInSilent; protected long _startup; + protected List _toBeInstalledResouces; + protected boolean _readyToInstall; + protected boolean _enableTracking = true; protected int _reportedProgress = 0; @@ -1284,6 +1295,4 @@ public abstract class Getdown extends Thread protected static final long PLAY_AGAIN_TIME = 3000L; protected static final String PROXY_REGISTRY = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"; - protected static List toBeInstalledResouces; - protected static boolean readyToInstall; } diff --git a/src/main/java/com/threerings/getdown/launcher/GetdownApp.java b/src/main/java/com/threerings/getdown/launcher/GetdownApp.java index 57818ff..08087c6 100644 --- a/src/main/java/com/threerings/getdown/launcher/GetdownApp.java +++ b/src/main/java/com/threerings/getdown/launcher/GetdownApp.java @@ -36,6 +36,20 @@ public class GetdownApp { public static void main (String[] argv) { + try { + start(argv); + } catch (Exception e) { + log.warning("main() failed.", e); + } + } + + /** + * Runs Getdown as an application, using the arguments supplie as {@code argv}. + * @return the {@code Getdown} instance that is running. {@link Getdown#start} will have been + * called on it. + * @throws Exception if anything goes wrong starting Getdown. + */ + public static Getdown start (String[] argv) throws Exception { int aidx = 0; List args = Arrays.asList(argv); @@ -91,118 +105,114 @@ public class GetdownApp log.info("-- Cur dir: " + System.getProperty("user.dir")); log.info("---------------------------------------------"); - try { - Getdown app = new Getdown(appDir, appId, null, null, appArgs) { - @Override - protected Container createContainer () { - // create our user interface, and display it - String title = StringUtil.isBlank(_ifc.name) ? "" : _ifc.name; - if (_frame == null) { - _frame = new JFrame(title); - _frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing (WindowEvent evt) { - handleWindowClose(); - } - }); - _frame.setUndecorated(_ifc.hideDecorations); - _frame.setResizable(false); - } else { - _frame.setTitle(title); - _frame.getContentPane().removeAll(); - } - - if (_ifc.iconImages != null) { - ArrayList icons = new ArrayList(); - for (String path : _ifc.iconImages) { - Image img = loadImage(path); - if (img == null) { - log.warning("Error loading icon image", "path", path); - } else { - icons.add(img); - } + Getdown app = new Getdown(appDir, appId, null, null, appArgs) { + @Override + protected Container createContainer () { + // create our user interface, and display it + String title = StringUtil.isBlank(_ifc.name) ? "" : _ifc.name; + if (_frame == null) { + _frame = new JFrame(title); + _frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing (WindowEvent evt) { + handleWindowClose(); } - if (icons.isEmpty()) { - log.warning("Failed to load any icons", "iconImages", _ifc.iconImages); + }); + _frame.setUndecorated(_ifc.hideDecorations); + _frame.setResizable(false); + } else { + _frame.setTitle(title); + _frame.getContentPane().removeAll(); + } + + if (_ifc.iconImages != null) { + ArrayList icons = new ArrayList(); + for (String path : _ifc.iconImages) { + Image img = loadImage(path); + if (img == null) { + log.warning("Error loading icon image", "path", path); } else { - SwingUtil.setFrameIcons(_frame, icons); + icons.add(img); } } - - _frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - return _frame.getContentPane(); - } - - @Override - protected void showContainer () { - if (_frame != null) { - _frame.pack(); - SwingUtil.centerWindow(_frame); - _frame.setVisible(true); - } - } - - @Override - protected void disposeContainer () { - if (_frame != null) { - _frame.dispose(); - _frame = null; - } - } - - @Override - protected void showDocument (String url) { - String[] cmdarray; - if (RunAnywhere.isWindows()) { - String osName = System.getProperty("os.name"); - if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) { - cmdarray = new String[] { - "command.com", "/c", "start", "\"" + url + "\"" }; - } else { - cmdarray = new String[] { - "cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" }; - } - } else if (RunAnywhere.isMacOS()) { - cmdarray = new String[] { "open", url }; - } else { // Linux, Solaris, etc. - cmdarray = new String[] { "firefox", url }; - } - try { - Runtime.getRuntime().exec(cmdarray); - } catch (Exception e) { - log.warning("Failed to open browser.", "cmdarray", cmdarray, e); - } - } - - @Override - protected void exit (int exitCode) { - // if we're running the app in the same JVM, don't call System.exit, but do - // make double sure that the download window is closed. - if (invokeDirect()) { - disposeContainer(); + if (icons.isEmpty()) { + log.warning("Failed to load any icons", "iconImages", _ifc.iconImages); } else { - System.exit(exitCode); + SwingUtil.setFrameIcons(_frame, icons); } } - @Override - protected void fail (String message) { - // if the frame was set to be undecorated, make window decoration available - // to allow the user to close the window - if (_frame != null && _frame.isUndecorated()) { - _frame.dispose(); - _frame.setUndecorated(false); - showContainer(); - } - super.fail(message); + _frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + return _frame.getContentPane(); + } + + @Override + protected void showContainer () { + if (_frame != null) { + _frame.pack(); + SwingUtil.centerWindow(_frame); + _frame.setVisible(true); } + } - protected JFrame _frame; - }; - app.start(); + @Override + protected void disposeContainer () { + if (_frame != null) { + _frame.dispose(); + _frame = null; + } + } - } catch (Exception e) { - log.warning("main() failed.", e); - } + @Override + protected void showDocument (String url) { + String[] cmdarray; + if (RunAnywhere.isWindows()) { + String osName = System.getProperty("os.name"); + if (osName.indexOf("9") != -1 || osName.indexOf("Me") != -1) { + cmdarray = new String[] { + "command.com", "/c", "start", "\"" + url + "\"" }; + } else { + cmdarray = new String[] { + "cmd.exe", "/c", "start", "\"\"", "\"" + url + "\"" }; + } + } else if (RunAnywhere.isMacOS()) { + cmdarray = new String[] { "open", url }; + } else { // Linux, Solaris, etc. + cmdarray = new String[] { "firefox", url }; + } + try { + Runtime.getRuntime().exec(cmdarray); + } catch (Exception e) { + log.warning("Failed to open browser.", "cmdarray", cmdarray, e); + } + } + + @Override + protected void exit (int exitCode) { + // if we're running the app in the same JVM, don't call System.exit, but do + // make double sure that the download window is closed. + if (invokeDirect()) { + disposeContainer(); + } else { + System.exit(exitCode); + } + } + + @Override + protected void fail (String message) { + // if the frame was set to be undecorated, make window decoration available + // to allow the user to close the window + if (_frame != null && _frame.isUndecorated()) { + _frame.dispose(); + _frame.setUndecorated(false); + showContainer(); + } + super.fail(message); + } + + protected JFrame _frame; + }; + app.start(); + return app; } } diff --git a/src/main/java/com/threerings/getdown/util/FileUtil.java b/src/main/java/com/threerings/getdown/util/FileUtil.java index 05b026d..67c0db9 100644 --- a/src/main/java/com/threerings/getdown/util/FileUtil.java +++ b/src/main/java/com/threerings/getdown/util/FileUtil.java @@ -60,7 +60,9 @@ public class FileUtil extends com.samskivert.util.FileUtil fin = new FileInputStream(source); fout = new FileOutputStream(dest); StreamUtil.copy(fin, fout); - fin.close(); // needed otherwise cannot delete source + // close the input stream now so we can delete 'source' + fin.close(); + fin = null; if (!source.delete()) { log.warning("Failed to delete " + source + " after brute force copy to " + dest + ".");