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; package com.threerings.getdown.data;
@@ -32,6 +32,8 @@ import org.apache.commons.io.CopyUtils;
import com.threerings.getdown.Log; import com.threerings.getdown.Log;
import com.threerings.getdown.util.ConfigUtil; 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 * 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 // now verify the contents of our main config file
Resource crsrc = getConfigResource(); Resource crsrc = getConfigResource();
if (!_digest.validateResource(crsrc)) { if (!_digest.validateResource(crsrc, null)) {
// attempt to redownload the file; again we pass errors up to // attempt to redownload the file; again we pass errors up to
// our caller because we have no recourse to recovery // our caller because we have no recourse to recovery
downloadControlFile(CONFIG_FILE); downloadControlFile(CONFIG_FILE);
// if the new copy validates, reinitialize ourselves; // if the new copy validates, reinitialize ourselves;
// otherwise report baffling hoseage // otherwise report baffling hoseage
if (_digest.validateResource(crsrc)) { if (_digest.validateResource(crsrc, null)) {
init(); init();
} }
} }
@@ -473,11 +475,46 @@ public class Application
* to go, null will be returned and the application is considered * to go, null will be returned and the application is considered
* ready to run. * ready to run.
*/ */
public List verifyResources () public List verifyResources (ProgressObserver obs)
{ {
ArrayList failures = new ArrayList(); ArrayList rsrcs = new ArrayList(), failures = new ArrayList();
verifyResources(_codes.iterator(), failures); rsrcs.addAll(_codes);
verifyResources(_resources.iterator(), failures); 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; return (failures.size() == 0) ? null : failures;
} }
@@ -500,30 +537,6 @@ public class Application
StringUtil.replace(_appbase, "%VERSION%", "" + version)); 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 /** Clears all validation marker files for the resources in the
* supplied iterator. */ * supplied iterator. */
protected void clearValidationMarkers (Iterator iter) 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; package com.threerings.getdown.data;
@@ -21,6 +21,7 @@ import com.samskivert.util.StringUtil;
import com.threerings.getdown.Log; import com.threerings.getdown.Log;
import com.threerings.getdown.util.ConfigUtil; import com.threerings.getdown.util.ConfigUtil;
import com.threerings.getdown.util.ProgressObserver;
/** /**
* Manages the <code>digest.txt</code> file and the computing and * 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 * digest check or if an I/O error was encountered during the
* validation process. * validation process.
*/ */
public boolean validateResource (Resource resource) public boolean validateResource (Resource resource, ProgressObserver obs)
{ {
try { try {
String cmd5 = resource.computeDigest(getMessageDigest()); String cmd5 = resource.computeDigest(getMessageDigest(), obs);
String emd5 = (String)_digests.get(resource.getPath()); String emd5 = (String)_digests.get(resource.getPath());
if (cmd5.equals(emd5)) { if (cmd5.equals(emd5)) {
return true; return true;
@@ -112,7 +113,7 @@ public class Digest
for (Iterator iter = resources.iterator(); iter.hasNext(); ) { for (Iterator iter = resources.iterator(); iter.hasNext(); ) {
Resource rsrc = (Resource)iter.next(); Resource rsrc = (Resource)iter.next();
String path = rsrc.getPath(); String path = rsrc.getPath();
String digest = rsrc.computeDigest(md); String digest = rsrc.computeDigest(md, null);
note(data, path, digest); note(data, path, digest);
pout.println(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; package com.threerings.getdown.data;
@@ -23,6 +23,7 @@ import com.samskivert.util.SortableArrayList;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
import com.threerings.getdown.Log; import com.threerings.getdown.Log;
import com.threerings.getdown.util.ProgressObserver;
/** /**
* Models a single file resource used by an {@link Application}. * 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. * Computes the MD5 hash of this resource's underlying file.
* <em>Note:</em> This is both CPU and I/O intensive. * <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 throws IOException
{ {
md.reset(); md.reset();
@@ -85,11 +86,13 @@ public class Resource
CollectionUtil.addAll(entries, jar.entries()); CollectionUtil.addAll(entries, jar.entries());
entries.sort(ENTRY_COMP); entries.sort(ENTRY_COMP);
int eidx = 0;
for (Iterator iter = entries.iterator(); iter.hasNext(); ) { for (Iterator iter = entries.iterator(); iter.hasNext(); ) {
JarEntry entry = (JarEntry)iter.next(); JarEntry entry = (JarEntry)iter.next();
// skip metadata; we just want the goods // skip metadata; we just want the goods
if (entry.getName().startsWith("META-INF")) { if (entry.getName().startsWith("META-INF")) {
updateProgress(obs, eidx, entries.size());
continue; continue;
} }
@@ -103,6 +106,7 @@ public class Resource
} finally { } finally {
StreamUtil.close(in); StreamUtil.close(in);
} }
updateProgress(obs, eidx, entries.size());
} }
} finally { } finally {
@@ -115,11 +119,14 @@ public class Resource
} }
} else { } else {
long totalSize = _local.length(), position = 0L;
FileInputStream fin = null; FileInputStream fin = null;
try { try {
fin = new FileInputStream(_local); fin = new FileInputStream(_local);
while ((read = fin.read(buffer)) != -1) { while ((read = fin.read(buffer)) != -1) {
md.update(buffer, 0, read); md.update(buffer, 0, read);
position += read;
updateProgress(obs, position, totalSize);
} }
} finally { } finally {
StreamUtil.close(fin); StreamUtil.close(fin);
@@ -208,6 +215,14 @@ public class Resource
return _path; 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 String _path;
protected URL _remote; protected URL _remote;
protected File _local, _marker; 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; package com.threerings.getdown.launcher;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Rectangle; import java.awt.Rectangle;
import java.awt.image.BufferedImage; 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.Application;
import com.threerings.getdown.data.Resource; import com.threerings.getdown.data.Resource;
import com.threerings.getdown.tools.Patcher; import com.threerings.getdown.tools.Patcher;
import com.threerings.getdown.util.ProgressObserver;
/** /**
* Manages the main control for the Getdown application updater and * Manages the main control for the Getdown application updater and
* deployment system. * deployment system.
*/ */
public class Getdown public class Getdown extends Thread
{ {
public Getdown (File appDir) public Getdown (File appDir)
{ {
super("Getdown");
_app = new Application(appDir); _app = new Application(appDir);
_msgs = ResourceBundle.getBundle("com.threerings.getdown.messages"); _msgs = ResourceBundle.getBundle("com.threerings.getdown.messages");
} }
@@ -57,6 +60,7 @@ public class Getdown
for (int ii = 0; ii < MAX_LOOPS; ii++) { for (int ii = 0; ii < MAX_LOOPS; ii++) {
// make sure we have the desired version and that the // make sure we have the desired version and that the
// metadata files are valid... // metadata files are valid...
setStatus("m.validating", -1, -1L);
if (_app.verifyMetadata()) { if (_app.verifyMetadata()) {
Log.info("Application requires update."); Log.info("Application requires update.");
update(); update();
@@ -65,7 +69,7 @@ public class Getdown
} }
// now verify our resources... // now verify our resources...
List failures = _app.verifyResources(); List failures = _app.verifyResources(_progobs);
if (failures == null) { if (failures == null) {
Log.info("Resources verified."); Log.info("Resources verified.");
launch(); launch();
@@ -97,7 +101,7 @@ public class Getdown
_app.clearValidationMarkers(); _app.clearValidationMarkers();
// attempt to download the patch file // attempt to download the patch file
Resource patch = _app.getPatchResource(); final Resource patch = _app.getPatchResource();
if (patch != null) { if (patch != null) {
// download the patch file... // download the patch file...
ArrayList list = new ArrayList(); ArrayList list = new ArrayList();
@@ -105,9 +109,10 @@ public class Getdown
download(list); download(list);
// and apply it... // and apply it...
setStatus("m.patching", -1, -1L);
Patcher patcher = new Patcher(); Patcher patcher = new Patcher();
patcher.patch(patch.getLocal().getParentFile(), patch.getLocal(), patcher.patch(patch.getLocal().getParentFile(),
null); patch.getLocal(), _progobs);
} }
// if the patch resource is null, that means something was booched // if the patch resource is null, that means something was booched
// in the application, so we skip the patching process but update // 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 // create a downloader to download our resources
Downloader.Observer obs = new Downloader.Observer() { Downloader.Observer obs = new Downloader.Observer() {
public void resolvingDownloads () { public void resolvingDownloads () {
_status.setStatus(_msgs.getString("m.resolving")); setStatus("m.resolving", -1, -1L);
} }
public void downloadProgress (int percent, long remaining) { public void downloadProgress (int percent, long remaining) {
_status.setStatus(_msgs.getString("m.downloading")); setStatus("m.downloading", percent, remaining);
_status.setProgress(percent, remaining);
if (percent == 100) { if (percent == 100) {
synchronized (lock) { synchronized (lock) {
lock.notify(); lock.notify();
@@ -150,7 +154,7 @@ public class Getdown
String msg = MessageFormat.format( String msg = MessageFormat.format(
_msgs.getString("m.failure"), _msgs.getString("m.failure"),
new Object[] { e.getMessage() }); new Object[] { e.getMessage() });
_status.setStatus(msg); setStatus(msg, -1, -1L);
Log.warning("Download failed [rsrc=" + rsrc + "]."); Log.warning("Download failed [rsrc=" + rsrc + "].");
Log.logStackTrace(e); Log.logStackTrace(e);
synchronized (lock) { synchronized (lock) {
@@ -177,9 +181,7 @@ public class Getdown
*/ */
protected void launch () protected void launch ()
{ {
if (_status != null) { setStatus("m.launching", 100, -1L);
_status.setStatus(_msgs.getString("m.launching"));
}
try { try {
Process proc = _app.createProcess(); Process proc = _app.createProcess();
@@ -227,6 +229,23 @@ public class Getdown
_frame.show(); _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) public static void main (String[] args)
{ {
// maybe they specified the appdir in a system property // maybe they specified the appdir in a system property
@@ -261,12 +280,19 @@ public class Getdown
try { try {
Getdown app = new Getdown(appDir); Getdown app = new Getdown(appDir);
app.run(); app.start();
} catch (Exception e) { } catch (Exception e) {
Log.logStackTrace(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 _app;
protected Application.UpdateInterface _ifc; 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 # Getdown translation messages
m.resolving = Resolving downloads... m.resolving = Resolving downloads...
m.downloading = Downloading data... m.downloading = Downloading data...
m.failure = Download failed: {0} m.failure = Download failed: {0}
m.launching = Launching application...
m.validating = Validating...
m.patching = Patching...
m.launching = Launching...
m.complete = {0}% complete m.complete = {0}% complete
m.complete_remain = {0}% complete {1}s remaining 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; package com.threerings.getdown.tools;
@@ -12,24 +12,22 @@ import java.util.Enumeration;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.zip.ZipEntry; 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.sun.javaws.jardiff.JarDiffPatcher;
import com.samskivert.io.StreamUtil;
import org.apache.commons.io.CopyUtils; import org.apache.commons.io.CopyUtils;
import com.threerings.getdown.util.ProgressObserver;
/** /**
* Applies a unified patch file to an application directory, providing * 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 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 * Applies the specified patch file to the application living in the
* specified application directory. The supplied observer, if * 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 * with the patcher so that the user interface is not blocked for the
* duration of the patch. * duration of the patch.
*/ */
public void patch (File appdir, File patch, Observer obs) public void patch (File appdir, File patch, ProgressObserver obs)
throws IOException throws IOException
{ {
// save this information for later
_obs = obs;
_plength = patch.length();
JarFile file = new JarFile(patch); JarFile file = new JarFile(patch);
Enumeration entries = file.entries(); // old skool! Enumeration entries = file.entries(); // old skool!
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry)entries.nextElement(); ZipEntry entry = (ZipEntry)entries.nextElement();
String path = entry.getName(); String path = entry.getName();
long elength = entry.getCompressedSize();
// depending on the suffix, we do The Right Thing (tm) // depending on the suffix, we do The Right Thing (tm)
if (path.endsWith(Differ.CREATE)) { if (path.endsWith(Differ.CREATE)) {
@@ -71,6 +74,9 @@ public class Patcher
} else { } else {
System.err.println("Skipping bogus patch file entry: " + path); 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) 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; InputStream in = null;
FileOutputStream fout = null; FileOutputStream fout = null;
try { try {
CopyUtils.copy(in = file.getInputStream(entry), in = file.getInputStream(entry);
fout = new FileOutputStream(target)); 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) { } catch (IOException ioe) {
System.err.println("Error creating '" + target + "': " + ioe); System.err.println("Error creating '" + target + "': " + ioe);
@@ -122,10 +139,20 @@ public class Patcher
return; 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 // now apply the patch to create the new target file
patcher = new JarDiffPatcher(); patcher = new JarDiffPatcher();
fout = new FileOutputStream(target); fout = new FileOutputStream(target);
patcher.applyPatch(null, otarget.getPath(), patch.getPath(), fout); patcher.applyPatch(pd, otarget.getPath(), patch.getPath(), fout);
} catch (IOException ioe) { } catch (IOException ioe) {
if (patcher == null) { 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) public static void main (String[] args)
{ {
if (args.length != 2) { if (args.length != 2) {
@@ -159,4 +193,10 @@ public class Patcher
System.exit(-1); 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);
}