diff --git a/src/java/com/threerings/getdown/data/Application.java b/src/java/com/threerings/getdown/data/Application.java
index 547d8e4..9c0ea44 100644
--- a/src/java/com/threerings/getdown/data/Application.java
+++ b/src/java/com/threerings/getdown/data/Application.java
@@ -1,5 +1,5 @@
//
-// $Id: Application.java,v 1.11 2004/07/14 12:11:46 mdb Exp $
+// $Id: Application.java,v 1.12 2004/07/14 13:44:49 mdb Exp $
package com.threerings.getdown.data;
@@ -32,6 +32,8 @@ import org.apache.commons.io.CopyUtils;
import com.threerings.getdown.Log;
import com.threerings.getdown.util.ConfigUtil;
+import com.threerings.getdown.util.MetaProgressObserver;
+import com.threerings.getdown.util.ProgressObserver;
/**
* Parses and provide access to the information contained in the
@@ -429,13 +431,13 @@ public class Application
// now verify the contents of our main config file
Resource crsrc = getConfigResource();
- if (!_digest.validateResource(crsrc)) {
+ if (!_digest.validateResource(crsrc, null)) {
// attempt to redownload the file; again we pass errors up to
// our caller because we have no recourse to recovery
downloadControlFile(CONFIG_FILE);
// if the new copy validates, reinitialize ourselves;
// otherwise report baffling hoseage
- if (_digest.validateResource(crsrc)) {
+ if (_digest.validateResource(crsrc, null)) {
init();
}
}
@@ -473,11 +475,46 @@ public class Application
* to go, null will be returned and the application is considered
* ready to run.
*/
- public List verifyResources ()
+ public List verifyResources (ProgressObserver obs)
{
- ArrayList failures = new ArrayList();
- verifyResources(_codes.iterator(), failures);
- verifyResources(_resources.iterator(), failures);
+ ArrayList rsrcs = new ArrayList(), failures = new ArrayList();
+ rsrcs.addAll(_codes);
+ rsrcs.addAll(_resources);
+
+ // total up the file size of the resources to validate
+ long totalSize = 0L;
+ for (Iterator iter = rsrcs.iterator(); iter.hasNext(); ) {
+ Resource rsrc = (Resource)iter.next();
+ totalSize += rsrc.getLocal().length();
+ }
+
+ MetaProgressObserver mpobs = new MetaProgressObserver(obs, totalSize);
+ for (Iterator iter = rsrcs.iterator(); iter.hasNext(); ) {
+ Resource rsrc = (Resource)iter.next();
+ mpobs.startElement(rsrc.getLocal().length());
+
+ if (rsrc.isMarkedValid()) {
+ mpobs.progress(100);
+ continue;
+ }
+
+ try {
+ if (_digest.validateResource(rsrc, mpobs)) {
+ // make a note that this file is kosher
+ rsrc.markAsValid();
+ continue;
+ }
+
+ } catch (Exception e) {
+ Log.info("Failure validating resource [rsrc=" + rsrc +
+ ", error=" + e + "]. Requesting redownload...");
+
+ } finally {
+ mpobs.progress(100);
+ }
+ failures.add(rsrc);
+ }
+
return (failures.size() == 0) ? null : failures;
}
@@ -500,30 +537,6 @@ public class Application
StringUtil.replace(_appbase, "%VERSION%", "" + version));
}
- /** A helper function used by {@link #verifyResources()}. */
- protected void verifyResources (Iterator rsrcs, List failures)
- {
- while (rsrcs.hasNext()) {
- Resource rsrc = (Resource)rsrcs.next();
- if (rsrc.isMarkedValid()) {
- continue;
- }
-
- try {
- if (_digest.validateResource(rsrc)) {
- // make a note that this file is kosher
- rsrc.markAsValid();
- continue;
- }
-
- } catch (Exception e) {
- Log.info("Failure validating resource [rsrc=" + rsrc +
- ", error=" + e + "]. Requesting redownload...");
- }
- failures.add(rsrc);
- }
- }
-
/** Clears all validation marker files for the resources in the
* supplied iterator. */
protected void clearValidationMarkers (Iterator iter)
diff --git a/src/java/com/threerings/getdown/data/Digest.java b/src/java/com/threerings/getdown/data/Digest.java
index 4952ccd..bc514a9 100644
--- a/src/java/com/threerings/getdown/data/Digest.java
+++ b/src/java/com/threerings/getdown/data/Digest.java
@@ -1,5 +1,5 @@
//
-// $Id: Digest.java,v 1.3 2004/07/06 09:46:35 mdb Exp $
+// $Id: Digest.java,v 1.4 2004/07/14 13:44:49 mdb Exp $
package com.threerings.getdown.data;
@@ -21,6 +21,7 @@ import com.samskivert.util.StringUtil;
import com.threerings.getdown.Log;
import com.threerings.getdown.util.ConfigUtil;
+import com.threerings.getdown.util.ProgressObserver;
/**
* Manages the digest.txt file and the computing and
@@ -79,10 +80,10 @@ public class Digest
* digest check or if an I/O error was encountered during the
* validation process.
*/
- public boolean validateResource (Resource resource)
+ public boolean validateResource (Resource resource, ProgressObserver obs)
{
try {
- String cmd5 = resource.computeDigest(getMessageDigest());
+ String cmd5 = resource.computeDigest(getMessageDigest(), obs);
String emd5 = (String)_digests.get(resource.getPath());
if (cmd5.equals(emd5)) {
return true;
@@ -112,7 +113,7 @@ public class Digest
for (Iterator iter = resources.iterator(); iter.hasNext(); ) {
Resource rsrc = (Resource)iter.next();
String path = rsrc.getPath();
- String digest = rsrc.computeDigest(md);
+ String digest = rsrc.computeDigest(md, null);
note(data, path, digest);
pout.println(path + " = " + digest);
}
diff --git a/src/java/com/threerings/getdown/data/Resource.java b/src/java/com/threerings/getdown/data/Resource.java
index 2412244..25cc502 100644
--- a/src/java/com/threerings/getdown/data/Resource.java
+++ b/src/java/com/threerings/getdown/data/Resource.java
@@ -1,5 +1,5 @@
//
-// $Id: Resource.java,v 1.9 2004/07/14 12:14:44 mdb Exp $
+// $Id: Resource.java,v 1.10 2004/07/14 13:44:49 mdb Exp $
package com.threerings.getdown.data;
@@ -23,6 +23,7 @@ import com.samskivert.util.SortableArrayList;
import com.samskivert.util.StringUtil;
import com.threerings.getdown.Log;
+import com.threerings.getdown.util.ProgressObserver;
/**
* Models a single file resource used by an {@link Application}.
@@ -68,7 +69,7 @@ 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)
+ public String computeDigest (MessageDigest md, ProgressObserver obs)
throws IOException
{
md.reset();
@@ -85,11 +86,13 @@ public class Resource
CollectionUtil.addAll(entries, jar.entries());
entries.sort(ENTRY_COMP);
+ int eidx = 0;
for (Iterator iter = entries.iterator(); iter.hasNext(); ) {
JarEntry entry = (JarEntry)iter.next();
// skip metadata; we just want the goods
if (entry.getName().startsWith("META-INF")) {
+ updateProgress(obs, eidx, entries.size());
continue;
}
@@ -103,6 +106,7 @@ public class Resource
} finally {
StreamUtil.close(in);
}
+ updateProgress(obs, eidx, entries.size());
}
} finally {
@@ -115,11 +119,14 @@ public class Resource
}
} else {
+ long totalSize = _local.length(), position = 0L;
FileInputStream fin = null;
try {
fin = new FileInputStream(_local);
while ((read = fin.read(buffer)) != -1) {
md.update(buffer, 0, read);
+ position += read;
+ updateProgress(obs, position, totalSize);
}
} finally {
StreamUtil.close(fin);
@@ -208,6 +215,14 @@ public class Resource
return _path;
}
+ /** Helper function to simplify the process of reporting progress. */
+ protected void updateProgress (ProgressObserver obs, long pos, long total)
+ {
+ if (obs != null) {
+ obs.progress((int)(100 * pos / total));
+ }
+ }
+
protected String _path;
protected URL _remote;
protected File _local, _marker;
diff --git a/src/java/com/threerings/getdown/launcher/Getdown.java b/src/java/com/threerings/getdown/launcher/Getdown.java
index 093131d..09919d5 100644
--- a/src/java/com/threerings/getdown/launcher/Getdown.java
+++ b/src/java/com/threerings/getdown/launcher/Getdown.java
@@ -1,9 +1,10 @@
//
-// $Id: Getdown.java,v 1.9 2004/07/13 17:45:40 mdb Exp $
+// $Id: Getdown.java,v 1.10 2004/07/14 13:44:49 mdb Exp $
package com.threerings.getdown.launcher;
import java.awt.BorderLayout;
+import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
@@ -29,15 +30,17 @@ import com.threerings.getdown.Log;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
import com.threerings.getdown.tools.Patcher;
+import com.threerings.getdown.util.ProgressObserver;
/**
* Manages the main control for the Getdown application updater and
* deployment system.
*/
-public class Getdown
+public class Getdown extends Thread
{
public Getdown (File appDir)
{
+ super("Getdown");
_app = new Application(appDir);
_msgs = ResourceBundle.getBundle("com.threerings.getdown.messages");
}
@@ -57,6 +60,7 @@ public class Getdown
for (int ii = 0; ii < MAX_LOOPS; ii++) {
// make sure we have the desired version and that the
// metadata files are valid...
+ setStatus("m.validating", -1, -1L);
if (_app.verifyMetadata()) {
Log.info("Application requires update.");
update();
@@ -65,7 +69,7 @@ public class Getdown
}
// now verify our resources...
- List failures = _app.verifyResources();
+ List failures = _app.verifyResources(_progobs);
if (failures == null) {
Log.info("Resources verified.");
launch();
@@ -97,7 +101,7 @@ public class Getdown
_app.clearValidationMarkers();
// attempt to download the patch file
- Resource patch = _app.getPatchResource();
+ final Resource patch = _app.getPatchResource();
if (patch != null) {
// download the patch file...
ArrayList list = new ArrayList();
@@ -105,9 +109,10 @@ public class Getdown
download(list);
// and apply it...
+ setStatus("m.patching", -1, -1L);
Patcher patcher = new Patcher();
- patcher.patch(patch.getLocal().getParentFile(), patch.getLocal(),
- null);
+ patcher.patch(patch.getLocal().getParentFile(),
+ patch.getLocal(), _progobs);
}
// if the patch resource is null, that means something was booched
// in the application, so we skip the patching process but update
@@ -133,12 +138,11 @@ public class Getdown
// create a downloader to download our resources
Downloader.Observer obs = new Downloader.Observer() {
public void resolvingDownloads () {
- _status.setStatus(_msgs.getString("m.resolving"));
+ setStatus("m.resolving", -1, -1L);
}
public void downloadProgress (int percent, long remaining) {
- _status.setStatus(_msgs.getString("m.downloading"));
- _status.setProgress(percent, remaining);
+ setStatus("m.downloading", percent, remaining);
if (percent == 100) {
synchronized (lock) {
lock.notify();
@@ -150,7 +154,7 @@ public class Getdown
String msg = MessageFormat.format(
_msgs.getString("m.failure"),
new Object[] { e.getMessage() });
- _status.setStatus(msg);
+ setStatus(msg, -1, -1L);
Log.warning("Download failed [rsrc=" + rsrc + "].");
Log.logStackTrace(e);
synchronized (lock) {
@@ -177,9 +181,7 @@ public class Getdown
*/
protected void launch ()
{
- if (_status != null) {
- _status.setStatus(_msgs.getString("m.launching"));
- }
+ setStatus("m.launching", 100, -1L);
try {
Process proc = _app.createProcess();
@@ -227,6 +229,23 @@ public class Getdown
_frame.show();
}
+ protected void setStatus (final String message, final int percent,
+ final long remaining)
+ {
+ if (_status != null) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run () {
+ if (message != null) {
+ _status.setStatus(_msgs.getString(message));
+ }
+ if (percent >= 0) {
+ _status.setProgress(percent, remaining);
+ }
+ }
+ });
+ }
+ }
+
public static void main (String[] args)
{
// maybe they specified the appdir in a system property
@@ -261,12 +280,19 @@ public class Getdown
try {
Getdown app = new Getdown(appDir);
- app.run();
+ app.start();
} catch (Exception e) {
Log.logStackTrace(e);
}
}
+ /** Used to pass progress on to our user interface. */
+ protected ProgressObserver _progobs = new ProgressObserver() {
+ public void progress (final int percent) {
+ setStatus(null, percent, -1L);
+ }
+ };
+
protected Application _app;
protected Application.UpdateInterface _ifc;
diff --git a/src/java/com/threerings/getdown/messages.properties b/src/java/com/threerings/getdown/messages.properties
index f0b2075..697cab9 100644
--- a/src/java/com/threerings/getdown/messages.properties
+++ b/src/java/com/threerings/getdown/messages.properties
@@ -1,12 +1,15 @@
#
-# $Id: messages.properties,v 1.1 2004/07/07 10:45:20 mdb Exp $
+# $Id: messages.properties,v 1.2 2004/07/14 13:44:49 mdb Exp $
#
# Getdown translation messages
m.resolving = Resolving downloads...
m.downloading = Downloading data...
m.failure = Download failed: {0}
-m.launching = Launching application...
+
+m.validating = Validating...
+m.patching = Patching...
+m.launching = Launching...
m.complete = {0}% complete
m.complete_remain = {0}% complete {1}s remaining
diff --git a/src/java/com/threerings/getdown/tools/Patcher.java b/src/java/com/threerings/getdown/tools/Patcher.java
index e4c71b6..ccb4eb6 100644
--- a/src/java/com/threerings/getdown/tools/Patcher.java
+++ b/src/java/com/threerings/getdown/tools/Patcher.java
@@ -1,5 +1,5 @@
//
-// $Id: Patcher.java,v 1.1 2004/07/13 17:45:40 mdb Exp $
+// $Id: Patcher.java,v 1.2 2004/07/14 13:44:49 mdb Exp $
package com.threerings.getdown.tools;
@@ -12,24 +12,22 @@ import java.util.Enumeration;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
-import com.samskivert.io.StreamUtil;
+import com.sun.javaws.cache.Patcher.PatchDelegate;
import com.sun.javaws.jardiff.JarDiffPatcher;
+
+import com.samskivert.io.StreamUtil;
import org.apache.commons.io.CopyUtils;
+import com.threerings.getdown.util.ProgressObserver;
+
/**
* Applies a unified patch file to an application directory, providing
- * percentage completion feedback along the way.
+ * percentage completion feedback along the way. Note: the
+ * patcher is not thread safe. Create a separate patcher instance for each
+ * patching action that is desired.
*/
public class Patcher
{
- /** Used to communicate patching progress. */
- public static interface Observer
- {
- /** Informs the observer that we have completed the specified
- * percentage of the patching process. */
- public void progress (int percent);
- }
-
/**
* Applies the specified patch file to the application living in the
* specified application directory. The supplied observer, if
@@ -40,14 +38,19 @@ public class Patcher
* with the patcher so that the user interface is not blocked for the
* duration of the patch.
*/
- public void patch (File appdir, File patch, Observer obs)
+ public void patch (File appdir, File patch, ProgressObserver obs)
throws IOException
{
+ // save this information for later
+ _obs = obs;
+ _plength = patch.length();
+
JarFile file = new JarFile(patch);
Enumeration entries = file.entries(); // old skool!
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry)entries.nextElement();
String path = entry.getName();
+ long elength = entry.getCompressedSize();
// depending on the suffix, we do The Right Thing (tm)
if (path.endsWith(Differ.CREATE)) {
@@ -71,6 +74,9 @@ public class Patcher
} else {
System.err.println("Skipping bogus patch file entry: " + path);
}
+
+ // note that we've completed this entry
+ _complete += elength;
}
}
@@ -81,11 +87,22 @@ public class Patcher
protected void createFile (JarFile file, ZipEntry entry, File target)
{
+ // create our copy buffer if necessary
+ if (_buffer == null) {
+ _buffer = new byte[COPY_BUFFER_SIZE];
+ }
+
InputStream in = null;
FileOutputStream fout = null;
try {
- CopyUtils.copy(in = file.getInputStream(entry),
- fout = new FileOutputStream(target));
+ in = file.getInputStream(entry);
+ fout = new FileOutputStream(target);
+ int total = 0, read;
+ while ((read = in.read(_buffer)) != -1) {
+ total += read;
+ fout.write(_buffer, 0, read);
+ updateProgress(total);
+ }
} catch (IOException ioe) {
System.err.println("Error creating '" + target + "': " + ioe);
@@ -122,10 +139,20 @@ public class Patcher
return;
}
+ // we'll need this to pass progress along to our observer
+ final String which = entry.getName();
+ final long elength = entry.getCompressedSize();
+ PatchDelegate pd = new PatchDelegate() {
+ public void patching (int percent) {
+ // System.out.println(which + ": " + percent);
+ updateProgress((int)(percent * elength / 100));
+ }
+ };
+
// now apply the patch to create the new target file
patcher = new JarDiffPatcher();
fout = new FileOutputStream(target);
- patcher.applyPatch(null, otarget.getPath(), patch.getPath(), fout);
+ patcher.applyPatch(pd, otarget.getPath(), patch.getPath(), fout);
} catch (IOException ioe) {
if (patcher == null) {
@@ -144,6 +171,13 @@ public class Patcher
}
}
+ protected void updateProgress (int progress)
+ {
+ if (_obs != null) {
+ _obs.progress((int)(100 * (_complete + progress) / _plength));
+ }
+ }
+
public static void main (String[] args)
{
if (args.length != 2) {
@@ -159,4 +193,10 @@ public class Patcher
System.exit(-1);
}
}
+
+ protected ProgressObserver _obs;
+ protected long _complete, _plength;
+ protected byte[] _buffer;
+
+ protected static final int COPY_BUFFER_SIZE = 4096;
}
diff --git a/src/java/com/threerings/getdown/util/MetaProgressObserver.java b/src/java/com/threerings/getdown/util/MetaProgressObserver.java
new file mode 100644
index 0000000..0cecefd
--- /dev/null
+++ b/src/java/com/threerings/getdown/util/MetaProgressObserver.java
@@ -0,0 +1,35 @@
+//
+// $Id: MetaProgressObserver.java,v 1.1 2004/07/14 13:44:49 mdb Exp $
+
+package com.threerings.getdown.util;
+
+/**
+ * Accumulates the progress from a number of elements into a single
+ * smoothly progressing progress.
+ */
+public class MetaProgressObserver implements ProgressObserver
+{
+ public MetaProgressObserver (ProgressObserver target, long totalSize)
+ {
+ _target = target;
+ _totalSize = totalSize;
+ }
+
+ public void startElement (long elementSize)
+ {
+ _currentSize += elementSize;
+ _elementSize = elementSize;
+ }
+
+ // documentation inherited from interface
+ public void progress (int percent)
+ {
+ if (_target != null && _elementSize > 0) {
+ long position = _currentSize + (100 * percent / _elementSize);
+ _target.progress((int)(100 * position / _totalSize));
+ }
+ }
+
+ protected ProgressObserver _target;
+ protected long _totalSize, _currentSize, _elementSize;
+}
diff --git a/src/java/com/threerings/getdown/util/ProgressObserver.java b/src/java/com/threerings/getdown/util/ProgressObserver.java
new file mode 100644
index 0000000..ad11f30
--- /dev/null
+++ b/src/java/com/threerings/getdown/util/ProgressObserver.java
@@ -0,0 +1,16 @@
+//
+// $Id: ProgressObserver.java,v 1.1 2004/07/14 13:44:49 mdb Exp $
+
+package com.threerings.getdown.util;
+
+/**
+ * Used to communicate progress.
+ */
+public interface ProgressObserver
+{
+ /**
+ * Informs the observer that we have completed the specified
+ * percentage of the process.
+ */
+ public void progress (int percent);
+}