From 4dc01dc7126844534553d57d59fb5b752456ae38 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 14 Jul 2004 13:44:49 +0000 Subject: [PATCH] Further cleanups. We now report progress to the best of our ability modulo some deplorable oversight on the part of JarDiffPatcher's author. --- .../threerings/getdown/data/Application.java | 75 +++++++++++-------- .../com/threerings/getdown/data/Digest.java | 9 ++- .../com/threerings/getdown/data/Resource.java | 19 ++++- .../threerings/getdown/launcher/Getdown.java | 54 +++++++++---- .../threerings/getdown/messages.properties | 7 +- .../com/threerings/getdown/tools/Patcher.java | 70 +++++++++++++---- .../getdown/util/MetaProgressObserver.java | 35 +++++++++ .../getdown/util/ProgressObserver.java | 16 ++++ 8 files changed, 217 insertions(+), 68 deletions(-) create mode 100644 src/java/com/threerings/getdown/util/MetaProgressObserver.java create mode 100644 src/java/com/threerings/getdown/util/ProgressObserver.java 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); +}