diff options
Diffstat (limited to 'tools/src/main/java/com/google/archivepatcher/tools/FileByFileTool.java')
-rw-r--r-- | tools/src/main/java/com/google/archivepatcher/tools/FileByFileTool.java | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/tools/src/main/java/com/google/archivepatcher/tools/FileByFileTool.java b/tools/src/main/java/com/google/archivepatcher/tools/FileByFileTool.java new file mode 100644 index 0000000..5c12c2e --- /dev/null +++ b/tools/src/main/java/com/google/archivepatcher/tools/FileByFileTool.java @@ -0,0 +1,232 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.archivepatcher.tools; + +import com.google.archivepatcher.applier.FileByFileV1DeltaApplier; +import com.google.archivepatcher.generator.DeltaFriendlyOldBlobSizeLimiter; +import com.google.archivepatcher.generator.FileByFileV1DeltaGenerator; +import com.google.archivepatcher.generator.RecommendationModifier; +import com.google.archivepatcher.generator.TotalRecompressionLimiter; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Simple command-line tool for generating and applying patches. + */ +public class FileByFileTool extends AbstractTool { + + /** Usage instructions for the command line. */ + private static final String USAGE = + "java -cp <classpath> com.google.archivepatcher.tools.FileByFileTool <options>\n" + + "\nOptions:\n" + + " --generate generate a patch\n" + + " --apply apply a patch\n" + + " --old the old file\n" + + " --new the new file\n" + + " --patch the patch file\n" + + " --trl optionally, the total bytes of recompression to allow (see below)\n" + + " --dfobsl optionally, a limit on the total size of the delta-friendly old blob (see below)\n" + + "\nTotal Recompression Limit (trl):\n" + + " When generating a patch, a limit can be specified on the total number of bytes to\n" + + " allow to be recompressed during the patch apply process. This can be for a variety\n" + + " of reasons, with the most obvious being to limit the amount of effort that has to\n" + + " be expended applying the patch on the target platform. To properly explain a\n" + + " patch that had such a limitation, it is necessary to specify the same limitation\n" + + " here. This argument is illegal for --apply, since it only applies to --generate.\n" + + "\nDelta Friendly Old Blob Size Limit (dfobsl):\n" + + " When generating a patch, a limit can be specified on the total size of the delta-\n" + + " friendly old blob. This implicitly limits the size of the temporary file that\n" + + " needs to be created when applying the patch. The size limit is \"soft\" in that \n" + + " the delta-friendly old blob needs to at least contain the original data that was\n" + + " within it; but the limit specified here will constrain any attempt to uncompress\n" + + " the content. If the limit is less than or equal to the size of the old file, no\n" + + " uncompression will be performed at all. Otherwise, the old file can expand into\n" + + " delta-friendly old blob until the size reaches this limit.\n" + + "\nExamples:\n" + + " To generate a patch from OLD to NEW, saving the patch in PATCH:\n" + + " java -cp <classpath> com.google.archivepatcher.tools.FileByFileTool --generate \\\n" + + " --old OLD --new NEW --patch PATCH\n" + + " To generate a patch from OLD to NEW, limiting to 1,000,000 recompress bytes:\n" + + " java -cp <classpath> com.google.archivepatcher.tools.FileByFileTool --generate \\\n" + + " --old OLD --new NEW --trl 1000000 --patch PATCH\n" + + " To apply a patch PATCH to OLD, saving the result in NEW:\n" + + " java -cp <classpath> com.google.archivepatcher.tools.FileByFileTool --apply \\\n" + + " --old OLD --patch PATCH --new NEW"; + + /** + * Modes of operation. + */ + private static enum Mode { + /** + * Generate a patch. + */ + GENERATE, + + /** + * Apply a patch. + */ + APPLY; + } + + /** + * Runs the tool. See usage instructions for more information. + * + * @param args command line arguments + * @throws IOException if anything goes wrong + * @throws InterruptedException if any thread has interrupted the current thread + */ + public static void main(String... args) throws IOException, InterruptedException { + new FileByFileTool().run(args); + } + + /** + * Run the tool. + * + * @param args command line arguments + * @throws IOException if anything goes wrong + * @throws InterruptedException if any thread has interrupted the current thread + */ + public void run(String... args) throws IOException, InterruptedException { + String oldPath = null; + String newPath = null; + String patchPath = null; + Long totalRecompressionLimit = null; + Long deltaFriendlyOldBlobSizeLimit = null; + Mode mode = null; + Iterator<String> argIterator = new LinkedList<String>(Arrays.asList(args)).iterator(); + while (argIterator.hasNext()) { + String arg = argIterator.next(); + if ("--old".equals(arg)) { + oldPath = popOrDie(argIterator, "--old"); + } else if ("--new".equals(arg)) { + newPath = popOrDie(argIterator, "--new"); + } else if ("--patch".equals(arg)) { + patchPath = popOrDie(argIterator, "--patch"); + } else if ("--generate".equals(arg)) { + mode = Mode.GENERATE; + } else if ("--apply".equals(arg)) { + mode = Mode.APPLY; + } else if ("--trl".equals(arg)) { + totalRecompressionLimit = Long.parseLong(popOrDie(argIterator, "--trl")); + if (totalRecompressionLimit < 0) { + exitWithUsage("--trl cannot be negative: " + totalRecompressionLimit); + } + } else if ("--dfobsl".equals(arg)) { + deltaFriendlyOldBlobSizeLimit = Long.parseLong(popOrDie(argIterator, "--dfobsl")); + if (deltaFriendlyOldBlobSizeLimit < 0) { + exitWithUsage("--dfobsl cannot be negative: " + deltaFriendlyOldBlobSizeLimit); + } + } else { + exitWithUsage("unknown argument: " + arg); + } + } + if (oldPath == null || newPath == null || patchPath == null || mode == null) { + exitWithUsage("missing required argument(s)"); + } + if (mode == Mode.APPLY && totalRecompressionLimit != null) { + exitWithUsage("--trl can only be used with --generate"); + } + if (mode == Mode.APPLY && deltaFriendlyOldBlobSizeLimit != null) { + exitWithUsage("--dfobsl can only be used with --generate"); + } + File oldFile = getRequiredFileOrDie(oldPath, "old file"); + if (mode == Mode.GENERATE) { + File newFile = getRequiredFileOrDie(newPath, "new file"); + generatePatch( + oldFile, + newFile, + new File(patchPath), + totalRecompressionLimit, + deltaFriendlyOldBlobSizeLimit); + } else { // mode == Mode.APPLY + File patchFile = getRequiredFileOrDie(patchPath, "patch file"); + applyPatch(oldFile, patchFile, new File(newPath)); + } + } + + /** + * Generate a specified patch to transform the specified old file to the specified new file. + * + * @param oldFile the old file (will be read) + * @param newFile the new file (will be read) + * @param patchFile the patch file (will be written) + * @param totalRecompressionLimit optional limit for total number of bytes of recompression to + * allow in the resulting patch + * @param deltaFriendlyOldBlobSizeLimit optional limit for the size of the delta-friendly old + * blob, which implies a limit on the temporary space needed to apply the generated patch + * @throws IOException if anything goes wrong + * @throws InterruptedException if any thread has interrupted the current thread + */ + public static void generatePatch( + File oldFile, + File newFile, + File patchFile, + Long totalRecompressionLimit, + Long deltaFriendlyOldBlobSizeLimit) + throws IOException, InterruptedException { + List<RecommendationModifier> recommendationModifiers = new ArrayList<RecommendationModifier>(); + if (totalRecompressionLimit != null) { + recommendationModifiers.add(new TotalRecompressionLimiter(totalRecompressionLimit)); + } + if (deltaFriendlyOldBlobSizeLimit != null) { + recommendationModifiers.add( + new DeltaFriendlyOldBlobSizeLimiter(deltaFriendlyOldBlobSizeLimit)); + } + FileByFileV1DeltaGenerator generator = + new FileByFileV1DeltaGenerator( + recommendationModifiers.toArray(new RecommendationModifier[] {})); + try (FileOutputStream patchOut = new FileOutputStream(patchFile); + BufferedOutputStream bufferedPatchOut = new BufferedOutputStream(patchOut)) { + generator.generateDelta(oldFile, newFile, bufferedPatchOut); + bufferedPatchOut.flush(); + } + } + + /** + * Apply a specified patch to the specified old file, creating the specified new file. + * @param oldFile the old file (will be read) + * @param patchFile the patch file (will be read) + * @param newFile the new file (will be written) + * @throws IOException if anything goes wrong + */ + public static void applyPatch(File oldFile, File patchFile, File newFile) throws IOException { + // Figure out temp directory + File tempFile = File.createTempFile("fbftool", "tmp"); + File tempDir = tempFile.getParentFile(); + tempFile.delete(); + FileByFileV1DeltaApplier applier = new FileByFileV1DeltaApplier(tempDir); + try (FileInputStream patchIn = new FileInputStream(patchFile); + BufferedInputStream bufferedPatchIn = new BufferedInputStream(patchIn); + FileOutputStream newOut = new FileOutputStream(newFile); + BufferedOutputStream bufferedNewOut = new BufferedOutputStream(newOut)) { + applier.applyDelta(oldFile, bufferedPatchIn, bufferedNewOut); + bufferedNewOut.flush(); + } + } + + @Override + protected String getUsage() { + return USAGE; + } +} |