Wired up the rest of the patching process. I need to rewrite the code that

computes MD5 hashes of jar files to be properly agnostic of the metadata
in the jar file and the order in which the files appear so that a jar file
patched with JarDiff hashes to the same value as one created arbitrarily.
Basically, I just want to hash the contents of the jar file in a canonical
order instead of hashing the actual jar file.
This commit is contained in:
Michael Bayne
2004-07-13 17:45:40 +00:00
parent 8d5592a9af
commit 70117c3489
4 changed files with 218 additions and 59 deletions
@@ -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; package com.threerings.getdown.data;
@@ -29,7 +29,7 @@ import com.samskivert.text.MessageUtil;
import com.samskivert.util.RunAnywhere; import com.samskivert.util.RunAnywhere;
import com.samskivert.util.StringUtil; 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.Log;
import com.threerings.getdown.util.ConfigUtil; import com.threerings.getdown.util.ConfigUtil;
@@ -562,7 +562,7 @@ public class Application
try { try {
fin = targetURL.openStream(); fin = targetURL.openStream();
fout = new FileOutputStream(target); fout = new FileOutputStream(target);
StreamUtils.pipe(fin, fout); CopyUtils.copy(fin, fout);
} finally { } finally {
StreamUtil.close(fin); StreamUtil.close(fin);
StreamUtil.close(fout); StreamUtil.close(fout);
@@ -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; package com.threerings.getdown.launcher;
@@ -20,7 +20,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; 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.swing.util.SwingUtil;
import com.samskivert.util.StringUtil; import com.samskivert.util.StringUtil;
@@ -28,6 +28,7 @@ import com.samskivert.util.StringUtil;
import com.threerings.getdown.Log; 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;
/** /**
* Manages the main control for the Getdown application updater and * Manages the main control for the Getdown application updater and
@@ -104,7 +105,9 @@ public class Getdown
download(list); download(list);
// and apply it... // 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 // 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
@@ -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; package com.threerings.getdown.tools;
@@ -8,12 +8,15 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import com.sun.javaws.jardiff.JarDiff; import com.sun.javaws.jardiff.JarDiff;
import org.apache.commons.io.CopyUtils;
import com.samskivert.io.StreamUtil; import com.samskivert.io.StreamUtil;
@@ -47,37 +50,33 @@ public class Differ
* <code>patchV.dat</code> where V is the old application version. * <code>patchV.dat</code> where V is the old application version.
*/ */
public void createDiff (File nvdir, File ovdir, boolean verbose) public void createDiff (File nvdir, File ovdir, boolean verbose)
throws IOException
{ {
// sanity check // sanity check
String nvers = nvdir.getName(); String nvers = nvdir.getName();
String overs = ovdir.getName(); String overs = ovdir.getName();
try { try {
if (Integer.parseInt(nvers) <= Integer.parseInt(overs)) { if (Integer.parseInt(nvers) <= Integer.parseInt(overs)) {
System.err.println("New version (" + nvers + ") must be " + String err = "New version (" + nvers + ") must be greater " +
"greater than old version (" + overs + ")."); "than old version (" + overs + ").";
System.exit(-1); throw new IOException(err);
} }
} catch (Exception e) { } catch (NumberFormatException nfe) {
System.err.println("Non-numeric versions? [nvers=" + nvers + throw new IOException("Non-numeric versions? [nvers=" + nvers +
", overs=" + overs + "]."); ", overs=" + overs + "].");
System.exit(-1);
} }
Application oapp = createApplication(ovdir); Application oapp = new Application(ovdir);
oapp.init();
ArrayList orsrcs = new ArrayList(); ArrayList orsrcs = new ArrayList();
orsrcs.addAll(oapp.getCodeResources()); orsrcs.addAll(oapp.getCodeResources());
orsrcs.addAll(oapp.getResources()); 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(); ArrayList nrsrcs = new ArrayList();
nrsrcs.addAll(napp.getCodeResources()); nrsrcs.addAll(napp.getCodeResources());
nrsrcs.addAll(napp.getResources()); nrsrcs.addAll(napp.getResources());
if (verbose) {
System.out.println(nrsrcs.size() + " new resources.");
}
File patch = new File(nvdir, "patch" + overs + ".dat"); File patch = new File(nvdir, "patch" + overs + ".dat");
JarOutputStream jout = null; JarOutputStream jout = null;
@@ -95,13 +94,13 @@ public class Differ
if (orsrc != null && rsrc.getPath().endsWith(".jar")) { if (orsrc != null && rsrc.getPath().endsWith(".jar")) {
if (verbose) { if (verbose) {
System.out.println( System.out.println(
"Generating jardiff: " + rsrc.getPath()); "JarDiff: " + rsrc.getPath());
} }
jout.putNextEntry(new ZipEntry(rsrc.getPath() + PATCH)); jout.putNextEntry(new ZipEntry(rsrc.getPath() + PATCH));
jarDiff(orsrc.getLocal(), rsrc.getLocal(), jout); jarDiff(orsrc.getLocal(), rsrc.getLocal(), jout);
} else { } else {
if (verbose) { if (verbose) {
System.out.println("New entry: " + rsrc.getPath()); System.out.println("Addition: " + rsrc.getPath());
} }
jout.putNextEntry(new ZipEntry(rsrc.getPath() + CREATE)); jout.putNextEntry(new ZipEntry(rsrc.getPath() + CREATE));
pipe(rsrc.getLocal(), jout); pipe(rsrc.getLocal(), jout);
@@ -114,46 +113,18 @@ public class Differ
// simply add an entry with the resource name and the // simply add an entry with the resource name and the
// deletion suffix // deletion suffix
if (verbose) { if (verbose) {
System.out.println("Removing entry: " + rsrc.getPath()); System.out.println("Removal: " + rsrc.getPath());
} }
jout.putNextEntry(new ZipEntry(rsrc.getPath() + DELETE)); jout.putNextEntry(new ZipEntry(rsrc.getPath() + DELETE));
} }
StreamUtil.close(jout); StreamUtil.close(jout);
System.out.println("Created patch file: " + patch);
} catch (Exception ioe) { } catch (IOException ioe) {
System.err.println("Failure creating patch file [patch=" + patch +
", error=" + ioe + "].");
StreamUtil.close(jout); StreamUtil.close(jout);
patch.delete(); patch.delete();
} throw ioe;
}
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);
} }
} }
@@ -165,11 +136,34 @@ public class Differ
public static void main (String[] args) public static void main (String[] args)
{ {
if (args.length != 2) { if (args.length < 2) {
System.err.println("Usage: Differ new_vers_dir old_vers_dir"); System.err.println(
"Usage: Differ [-verbose] new_vers_dir old_vers_dir");
System.exit(-1); System.exit(-1);
} }
Differ differ = new Differ(); 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);
}
} }
} }
@@ -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.
*
* <p><em>Note:</em> 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);
}
}
}