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