X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=getdown%2Fsrc%2Fgetdown%2Fcore%2Fsrc%2Fmain%2Fjava%2Fcom%2Fthreerings%2Fgetdown%2Ftools%2FPatcher.java;fp=getdown%2Fsrc%2Fgetdown%2Fcore%2Fsrc%2Fmain%2Fjava%2Fcom%2Fthreerings%2Fgetdown%2Ftools%2FPatcher.java;h=4ead59bb34318044b895f55a8c1d8911f43f14c1;hb=74393b51f368cb9f58589472d432a433d9c4386d;hp=0000000000000000000000000000000000000000;hpb=7a0d503181fe41452120a8a02ca63476392aa08c;p=jalview.git
diff --git a/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java
new file mode 100644
index 0000000..4ead59b
--- /dev/null
+++ b/getdown/src/getdown/core/src/main/java/com/threerings/getdown/tools/Patcher.java
@@ -0,0 +1,205 @@
+//
+// Getdown - application installer, patcher and launcher
+// Copyright (C) 2004-2018 Getdown authors
+// https://github.com/threerings/getdown/blob/master/LICENSE
+
+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.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import com.threerings.getdown.util.FileUtil;
+import com.threerings.getdown.util.ProgressObserver;
+import com.threerings.getdown.util.StreamUtil;
+
+import static com.threerings.getdown.Log.log;
+
+/**
+ * Applies a unified patch file to an application directory, providing
+ * 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
+{
+ /** A suffix appended to file names to indicate that a file should be newly created. */
+ public static final String CREATE = ".create";
+
+ /** A suffix appended to file names to indicate that a file should be patched. */
+ public static final String PATCH = ".patch";
+
+ /** A suffix appended to file names to indicate that a file should be deleted. */
+ public static final String DELETE = ".delete";
+
+ /**
+ * 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, ProgressObserver obs)
+ throws IOException
+ {
+ // save this information for later
+ _obs = obs;
+ _plength = patch.length();
+
+ try (JarFile file = new JarFile(patch)) {
+ Enumeration entries = file.entries(); // old skool!
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ String path = entry.getName();
+ long elength = entry.getCompressedSize();
+
+ // depending on the suffix, we do The Right Thing (tm)
+ if (path.endsWith(CREATE)) {
+ path = strip(path, CREATE);
+ log.info("Creating " + path + "...");
+ createFile(file, entry, new File(appdir, path));
+
+ } else if (path.endsWith(PATCH)) {
+ path = strip(path, PATCH);
+ log.info("Patching " + path + "...");
+ patchFile(file, entry, appdir, path);
+
+ } else if (path.endsWith(DELETE)) {
+ path = strip(path, DELETE);
+ log.info("Removing " + path + "...");
+ File target = new File(appdir, path);
+ if (!FileUtil.deleteHarder(target)) {
+ log.warning("Failure deleting '" + target + "'.");
+ }
+
+ } else {
+ log.warning("Skipping bogus patch file entry: " + path);
+ }
+
+ // note that we've completed this entry
+ _complete += elength;
+ }
+ }
+ }
+
+ protected String strip (String path, String suffix)
+ {
+ return path.substring(0, path.length() - suffix.length());
+ }
+
+ protected void createFile (JarFile file, ZipEntry entry, File target)
+ {
+ // create our copy buffer if necessary
+ if (_buffer == null) {
+ _buffer = new byte[COPY_BUFFER_SIZE];
+ }
+
+ // make sure the file's parent directory exists
+ File pdir = target.getParentFile();
+ if (!pdir.exists() && !pdir.mkdirs()) {
+ log.warning("Failed to create parent for '" + target + "'.");
+ }
+
+ try (InputStream in = file.getInputStream(entry);
+ FileOutputStream 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) {
+ log.warning("Error creating '" + target + "': " + ioe);
+ }
+ }
+
+ 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
+ FileUtil.deleteHarder(otarget);
+
+ // pipe the contents of the patch into a file
+ try (InputStream in = file.getInputStream(entry);
+ FileOutputStream fout = new FileOutputStream(patch)) {
+
+ StreamUtil.copy(in, fout);
+ StreamUtil.close(fout);
+
+ // move the current version of the jar to .old
+ if (!FileUtil.renameTo(target, otarget)) {
+ log.warning("Failed to .oldify '" + target + "'.");
+ return;
+ }
+
+ // we'll need this to pass progress along to our observer
+ final long elength = entry.getCompressedSize();
+ ProgressObserver obs = new ProgressObserver() {
+ public void progress (int percent) {
+ updateProgress((int)(percent * elength / 100));
+ }
+ };
+
+ // now apply the patch to create the new target file
+ patcher = new JarDiffPatcher();
+ patcher.patchJar(otarget.getPath(), patch.getPath(), target, obs);
+
+ } catch (IOException ioe) {
+ if (patcher == null) {
+ log.warning("Failed to write patch file '" + patch + "': " + ioe);
+ } else {
+ log.warning("Error patching '" + target + "': " + ioe);
+ }
+
+ } finally {
+ // clean up our temporary files
+ FileUtil.deleteHarder(patch);
+ FileUtil.deleteHarder(otarget);
+ }
+ }
+
+ 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) {
+ 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);
+ }
+ }
+
+ protected ProgressObserver _obs;
+ protected long _complete, _plength;
+ protected byte[] _buffer;
+
+ protected static final int COPY_BUFFER_SIZE = 4096;
+}