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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
* <code>patchV.dat</code> 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 +
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Non-numeric versions? [nvers=" + nvers +
|
||||
", overs=" + overs + "].");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user