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); + } + } +}