diff --git a/src/java/com/threerings/getdown/data/Application.java b/src/java/com/threerings/getdown/data/Application.java
index 2aac61a..27e4a96 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.9 2004/07/13 02:42:52 mdb Exp $
+// $Id: Application.java,v 1.10 2004/07/13 17:45:40 mdb Exp $
package com.threerings.getdown.data;
@@ -29,7 +29,7 @@ import com.samskivert.text.MessageUtil;
import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil;
-import org.apache.commons.io.StreamUtils;
+import org.apache.commons.io.CopyUtils;
import com.threerings.getdown.Log;
import com.threerings.getdown.util.ConfigUtil;
@@ -562,7 +562,7 @@ public class Application
try {
fin = targetURL.openStream();
fout = new FileOutputStream(target);
- StreamUtils.pipe(fin, fout);
+ CopyUtils.copy(fin, fout);
} finally {
StreamUtil.close(fin);
StreamUtil.close(fout);
diff --git a/src/java/com/threerings/getdown/launcher/Getdown.java b/src/java/com/threerings/getdown/launcher/Getdown.java
index 1bb80e9..093131d 100644
--- a/src/java/com/threerings/getdown/launcher/Getdown.java
+++ b/src/java/com/threerings/getdown/launcher/Getdown.java
@@ -1,5 +1,5 @@
//
-// $Id: Getdown.java,v 1.8 2004/07/13 10:39:50 mdb Exp $
+// $Id: Getdown.java,v 1.9 2004/07/13 17:45:40 mdb Exp $
package com.threerings.getdown.launcher;
@@ -20,7 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
-import org.apache.commons.io.TeeOutputStream;
+// import org.apache.commons.io.TeeOutputStream;
import com.samskivert.swing.util.SwingUtil;
import com.samskivert.util.StringUtil;
@@ -28,6 +28,7 @@ import com.samskivert.util.StringUtil;
import com.threerings.getdown.Log;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Resource;
+import com.threerings.getdown.tools.Patcher;
/**
* Manages the main control for the Getdown application updater and
@@ -104,7 +105,9 @@ public class Getdown
download(list);
// and apply it...
- // TODO
+ Patcher patcher = new Patcher();
+ patcher.patch(patch.getLocal().getParentFile(), patch.getLocal(),
+ null);
}
// if the patch resource is null, that means something was booched
// in the application, so we skip the patching process but update
diff --git a/src/java/com/threerings/getdown/tools/Differ.java b/src/java/com/threerings/getdown/tools/Differ.java
index d4fd332..7fb51d1 100644
--- a/src/java/com/threerings/getdown/tools/Differ.java
+++ b/src/java/com/threerings/getdown/tools/Differ.java
@@ -1,5 +1,5 @@
//
-// $Id: Differ.java,v 1.2 2004/07/13 15:52:18 ray Exp $
+// $Id: Differ.java,v 1.3 2004/07/13 17:45:40 mdb Exp $
package com.threerings.getdown.tools;
@@ -8,12 +8,15 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import com.sun.javaws.jardiff.JarDiff;
+import org.apache.commons.io.CopyUtils;
import com.samskivert.io.StreamUtil;
@@ -47,37 +50,33 @@ public class Differ
* patchV.dat where V is the old application version.
*/
public void createDiff (File nvdir, File ovdir, boolean verbose)
+ throws IOException
{
// sanity check
String nvers = nvdir.getName();
String overs = ovdir.getName();
try {
if (Integer.parseInt(nvers) <= Integer.parseInt(overs)) {
- System.err.println("New version (" + nvers + ") must be " +
- "greater than old version (" + overs + ").");
- System.exit(-1);
+ String err = "New version (" + nvers + ") must be greater " +
+ "than old version (" + overs + ").";
+ throw new IOException(err);
}
- } catch (Exception e) {
- System.err.println("Non-numeric versions? [nvers=" + nvers +
- ", overs=" + overs + "].");
- System.exit(-1);
+ } catch (NumberFormatException nfe) {
+ throw new IOException("Non-numeric versions? [nvers=" + nvers +
+ ", overs=" + overs + "].");
}
- Application oapp = createApplication(ovdir);
+ Application oapp = new Application(ovdir);
+ oapp.init();
ArrayList orsrcs = new ArrayList();
orsrcs.addAll(oapp.getCodeResources());
orsrcs.addAll(oapp.getResources());
- if (verbose) {
- System.out.println(orsrcs.size() + " old resources.");
- }
- Application napp = createApplication(nvdir);
+ Application napp = new Application(nvdir);
+ napp.init();
ArrayList nrsrcs = new ArrayList();
nrsrcs.addAll(napp.getCodeResources());
nrsrcs.addAll(napp.getResources());
- if (verbose) {
- System.out.println(nrsrcs.size() + " new resources.");
- }
File patch = new File(nvdir, "patch" + overs + ".dat");
JarOutputStream jout = null;
@@ -95,13 +94,13 @@ public class Differ
if (orsrc != null && rsrc.getPath().endsWith(".jar")) {
if (verbose) {
System.out.println(
- "Generating jardiff: " + rsrc.getPath());
+ "JarDiff: " + rsrc.getPath());
}
jout.putNextEntry(new ZipEntry(rsrc.getPath() + PATCH));
jarDiff(orsrc.getLocal(), rsrc.getLocal(), jout);
} else {
if (verbose) {
- System.out.println("New entry: " + rsrc.getPath());
+ System.out.println("Addition: " + rsrc.getPath());
}
jout.putNextEntry(new ZipEntry(rsrc.getPath() + CREATE));
pipe(rsrc.getLocal(), jout);
@@ -114,46 +113,18 @@ public class Differ
// simply add an entry with the resource name and the
// deletion suffix
if (verbose) {
- System.out.println("Removing entry: " + rsrc.getPath());
+ System.out.println("Removal: " + rsrc.getPath());
}
jout.putNextEntry(new ZipEntry(rsrc.getPath() + DELETE));
}
StreamUtil.close(jout);
+ System.out.println("Created patch file: " + patch);
- } catch (Exception ioe) {
- System.err.println("Failure creating patch file [patch=" + patch +
- ", error=" + ioe + "].");
+ } catch (IOException ioe) {
StreamUtil.close(jout);
patch.delete();
- }
- }
-
- protected Application createApplication (File dir)
- {
- Application app = new Application(dir);
- try {
- app.init();
- } catch (IOException ioe) {
- System.err.println("Invalid app directory '" + dir + "': " + ioe);
- System.exit(-1);
- }
- return app;
- }
-
- protected void pipe (File file, JarOutputStream jout)
- throws IOException
- {
- FileInputStream fin = null;
- try {
- fin = new FileInputStream(file);
- byte[] buffer = new byte[4096];
- int read;
- while ((read = fin.read(buffer)) >= 0) {
- jout.write(buffer, 0, read);
- }
- } finally {
- StreamUtil.close(fin);
+ throw ioe;
}
}
@@ -165,11 +136,34 @@ public class Differ
public static void main (String[] args)
{
- if (args.length != 2) {
- System.err.println("Usage: Differ new_vers_dir old_vers_dir");
+ if (args.length < 2) {
+ System.err.println(
+ "Usage: Differ [-verbose] new_vers_dir old_vers_dir");
System.exit(-1);
}
Differ differ = new Differ();
- differ.createDiff(new File(args[0]), new File(args[1]), false);
+ boolean verbose = false;
+ int aidx = 0;
+ if (args[0].equals("-verbose")) {
+ verbose = true;
+ aidx++;
+ }
+ try {
+ differ.createDiff(new File(args[aidx++]),
+ new File(args[aidx++]), verbose);
+ } catch (IOException ioe) {
+ System.err.println("Error: " + ioe.getMessage());
+ }
+ }
+
+ protected static void pipe (File file, JarOutputStream jout)
+ throws IOException
+ {
+ FileInputStream fin = null;
+ try {
+ CopyUtils.copy(fin = new FileInputStream(file), jout);
+ } finally {
+ StreamUtil.close(fin);
+ }
}
}
diff --git a/src/java/com/threerings/getdown/tools/Patcher.java b/src/java/com/threerings/getdown/tools/Patcher.java
new file mode 100644
index 0000000..e4c71b6
--- /dev/null
+++ b/src/java/com/threerings/getdown/tools/Patcher.java
@@ -0,0 +1,162 @@
+//
+// $Id: Patcher.java,v 1.1 2004/07/13 17:45:40 mdb Exp $
+
+package com.threerings.getdown.tools;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.Enumeration;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import com.samskivert.io.StreamUtil;
+import com.sun.javaws.jardiff.JarDiffPatcher;
+import org.apache.commons.io.CopyUtils;
+
+/**
+ * Applies a unified patch file to an application directory, providing
+ * percentage completion feedback along the way.
+ */
+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
+ * non-null, will be notified of progress along the way.
+ *
+ *
Note: this method runs on the calling thread, thus the + * caller may want to make use of a separate thread in conjunction + * 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) + throws IOException + { + JarFile file = new JarFile(patch); + Enumeration entries = file.entries(); // old skool! + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry)entries.nextElement(); + String path = entry.getName(); + + // depending on the suffix, we do The Right Thing (tm) + if (path.endsWith(Differ.CREATE)) { + path = strip(path, Differ.CREATE); + System.out.println("Creating " + path + "..."); + createFile(file, entry, new File(appdir, path)); + + } else if (path.endsWith(Differ.PATCH)) { + path = strip(path, Differ.PATCH); + System.out.println("Patching " + path + "..."); + patchFile(file, entry, appdir, path); + + } else if (path.endsWith(Differ.DELETE)) { + path = strip(path, Differ.DELETE); + System.out.println("Removing " + path + "..."); + File target = new File(appdir, path); + if (!target.delete()) { + System.err.println("Failure deleting '" + target + "'."); + } + + } else { + System.err.println("Skipping bogus patch file entry: " + path); + } + } + } + + protected String strip (String path, String suffix) + { + return path.substring(0, path.length() - suffix.length()); + } + + protected void createFile (JarFile file, ZipEntry entry, File target) + { + InputStream in = null; + FileOutputStream fout = null; + try { + CopyUtils.copy(in = file.getInputStream(entry), + fout = new FileOutputStream(target)); + + } catch (IOException ioe) { + System.err.println("Error creating '" + target + "': " + ioe); + + } finally { + StreamUtil.close(in); + StreamUtil.close(fout); + } + } + + protected void patchFile (JarFile file, ZipEntry entry, + File appdir, String path) + { + File target = new File(appdir, path); + File patch = new File(appdir, entry.getName()); + File otarget = new File(appdir, path + ".old"); + JarDiffPatcher patcher = null; + + // make sure no stale old target is lying around to mess us up + otarget.delete(); + + // pipe the contents of the patch into a file + InputStream in = null; + FileOutputStream fout = null; + try { + CopyUtils.copy(in = file.getInputStream(entry), + fout = new FileOutputStream(patch)); + StreamUtil.close(fout); + fout = null; + + // move the current version of the jar to .old + if (!target.renameTo(otarget)) { + System.err.println("Failed to .oldify '" + target + "'."); + return; + } + + // 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); + + } catch (IOException ioe) { + if (patcher == null) { + System.err.println("Failed to write patch file '" + patch + + "': " + ioe); + } else { + System.err.println("Error patching '" + target + "': " + ioe); + } + + } finally { + StreamUtil.close(fout); + StreamUtil.close(in); + // clean up our temporary files + patch.delete(); + otarget.delete(); + } + } + + public static void main (String[] args) + { + if (args.length != 2) { + System.err.println("Usage: Patcher appdir patch_file"); + System.exit(-1); + } + + Patcher patcher = new Patcher(); + try { + patcher.patch(new File(args[0]), new File(args[1]), null); + } catch (IOException ioe) { + System.err.println("Error: " + ioe.getMessage()); + System.exit(-1); + } + } +}