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 + ".");