1 // Copyright 2016 Google Inc. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.archivepatcher.generator;
16 
17 import com.google.archivepatcher.generator.bsdiff.BsDiffDeltaGenerator;
18 import java.io.BufferedOutputStream;
19 import java.io.File;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.List;
26 
27 /**
28  * Generates file-by-file patches.
29  */
30 public class FileByFileV1DeltaGenerator implements DeltaGenerator {
31 
32   /** Optional modifiers for planning and patch generation. */
33   private final List<RecommendationModifier> recommendationModifiers;
34 
35   /**
36    * Constructs a new generator for File-by-File v1 patches, using the specified configuration.
37    *
38    * @param recommendationModifiers optionally, {@link RecommendationModifier}s to use for modifying
39    *     the planning phase of patch generation. These can be used to, e.g., limit the total amount
40    *     of recompression that a patch applier needs to do. Modifiers are applied in the order they
41    *     are specified.
42    */
FileByFileV1DeltaGenerator(RecommendationModifier... recommendationModifiers)43   public FileByFileV1DeltaGenerator(RecommendationModifier... recommendationModifiers) {
44     if (recommendationModifiers != null) {
45       this.recommendationModifiers =
46           Collections.unmodifiableList(Arrays.asList(recommendationModifiers));
47     } else {
48       this.recommendationModifiers = Collections.emptyList();
49     }
50   }
51 
52   /**
53    * Generate a V1 patch for the specified input files and write the patch to the specified {@link
54    * OutputStream}. The written patch is <em>raw</em>, i.e. it has not been compressed. Compression
55    * should almost always be applied to the patch, either right in the specified {@link
56    * OutputStream} or in a post-processing step, prior to transmitting the patch to the patch
57    * applier.
58    *
59    * @param oldFile the original old file to read (will not be modified)
60    * @param newFile the original new file to read (will not be modified)
61    * @param patchOut the stream to write the patch to
62    * @throws IOException if unable to complete the operation due to an I/O error
63    * @throws InterruptedException if any thread has interrupted the current thread
64    */
65   @Override
generateDelta(File oldFile, File newFile, OutputStream patchOut)66   public void generateDelta(File oldFile, File newFile, OutputStream patchOut)
67       throws IOException, InterruptedException {
68     try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder();
69         TempFileHolder deltaFriendlyNewFile = new TempFileHolder();
70         TempFileHolder deltaFile = new TempFileHolder();
71         FileOutputStream deltaFileOut = new FileOutputStream(deltaFile.file);
72         BufferedOutputStream bufferedDeltaOut = new BufferedOutputStream(deltaFileOut)) {
73       PreDiffExecutor.Builder builder =
74           new PreDiffExecutor.Builder()
75               .readingOriginalFiles(oldFile, newFile)
76               .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file);
77       for (RecommendationModifier modifier : recommendationModifiers) {
78         builder.withRecommendationModifier(modifier);
79       }
80       PreDiffExecutor executor = builder.build();
81       PreDiffPlan preDiffPlan = executor.prepareForDiffing();
82       DeltaGenerator deltaGenerator = getDeltaGenerator();
83       deltaGenerator.generateDelta(
84           deltaFriendlyOldFile.file, deltaFriendlyNewFile.file, bufferedDeltaOut);
85       bufferedDeltaOut.close();
86       PatchWriter patchWriter =
87           new PatchWriter(
88               preDiffPlan,
89               deltaFriendlyOldFile.file.length(),
90               deltaFriendlyNewFile.file.length(),
91               deltaFile.file);
92       patchWriter.writeV1Patch(patchOut);
93     }
94   }
95 
96   /**
97    * Generate a V1 patch pre diffing plan.
98    *
99    * @param oldFile the original old file to read (will not be modified)
100    * @param newFile the original new file to read (will not be modified)
101    * @return the plan
102    * @throws IOException if unable to complete the operation due to an I/O error
103    * @throws InterruptedException if any thread has interrupted the current thread
104    */
generatePreDiffPlan(File oldFile, File newFile)105   public PreDiffPlan generatePreDiffPlan(File oldFile, File newFile)
106       throws IOException, InterruptedException {
107     try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder();
108         TempFileHolder deltaFriendlyNewFile = new TempFileHolder()) {
109       PreDiffExecutor.Builder builder =
110           new PreDiffExecutor.Builder()
111               .readingOriginalFiles(oldFile, newFile)
112               .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file);
113       for (RecommendationModifier modifier : recommendationModifiers) {
114         builder.withRecommendationModifier(modifier);
115       }
116 
117       PreDiffExecutor executor = builder.build();
118 
119       return executor.prepareForDiffing();
120     }
121   }
122 
123   // Visible for testing only
getDeltaGenerator()124   protected DeltaGenerator getDeltaGenerator() {
125     return new BsDiffDeltaGenerator();
126   }
127 }
128