Further cleanups. We now report progress to the best of our ability modulo

some deplorable oversight on the part of JarDiffPatcher's author.
This commit is contained in:
Michael Bayne
2004-07-14 13:44:49 +00:00
parent b5e2086182
commit 4dc01dc712
8 changed files with 217 additions and 68 deletions
@@ -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)
@@ -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 <code>digest.txt</code> 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);
}
@@ -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.
* <em>Note:</em> 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;
@@ -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;
@@ -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
@@ -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. <em>Note:</em> 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;
}
@@ -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;
}
@@ -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);
}