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