• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.shared.JreDeflateParameters;
18 import com.google.archivepatcher.shared.PatchConstants;
19 import com.google.archivepatcher.shared.TypedRange;
20 import java.io.BufferedInputStream;
21 import java.io.DataOutputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 
27 /**
28  * Writes patches.
29  */
30 public class PatchWriter {
31   /**
32    * The patch plan.
33    */
34   private final PreDiffPlan plan;
35 
36   /**
37    * The expected size of the delta-friendly old file, provided as a convenience for the patch
38    * <strong>applier</strong> to reserve space on the filesystem for applying the patch.
39    */
40   private final long deltaFriendlyOldFileSize;
41 
42   /**
43    * The expected size of the delta-friendly new file, provided for forward compatibility.
44    */
45   private final long deltaFriendlyNewFileSize;
46 
47   /**
48    * The delta that transforms the old delta-friendly file into the new delta-friendly file.
49    */
50   private final File deltaFile;
51 
52   /**
53    * Creates a new patch writer.
54    *
55    * @param plan the patch plan
56    * @param deltaFriendlyOldFileSize the expected size of the delta-friendly old file, provided as a
57    *     convenience for the patch <strong>applier</strong> to reserve space on the filesystem for
58    *     applying the patch
59    * @param deltaFriendlyNewFileSize the expected size of the delta-friendly new file, provided for
60    *     forward compatibility
61    * @param deltaFile the delta that transforms the old delta-friendly file into the new
62    *     delta-friendly file
63    */
PatchWriter( PreDiffPlan plan, long deltaFriendlyOldFileSize, long deltaFriendlyNewFileSize, File deltaFile)64   public PatchWriter(
65       PreDiffPlan plan,
66       long deltaFriendlyOldFileSize,
67       long deltaFriendlyNewFileSize,
68       File deltaFile) {
69     this.plan = plan;
70     this.deltaFriendlyOldFileSize = deltaFriendlyOldFileSize;
71     this.deltaFriendlyNewFileSize = deltaFriendlyNewFileSize;
72     this.deltaFile = deltaFile;
73   }
74 
75   /**
76    * Write a v1-style patch to the specified output stream.
77    * @param out the stream to write the patch to
78    * @throws IOException if anything goes wrong
79    */
writeV1Patch(OutputStream out)80   public void writeV1Patch(OutputStream out) throws IOException {
81     // Use DataOutputStream for ease of writing. This is deliberately left open, as closing it would
82     // close the output stream that was passed in and that is not part of the method's documented
83     // behavior.
84     @SuppressWarnings("resource")
85     DataOutputStream dataOut = new DataOutputStream(out);
86 
87     dataOut.write(PatchConstants.IDENTIFIER.getBytes("US-ASCII"));
88     dataOut.writeInt(0); // Flags (reserved)
89     dataOut.writeLong(deltaFriendlyOldFileSize);
90 
91     // Write out all the delta-friendly old file uncompression instructions
92     dataOut.writeInt(plan.getOldFileUncompressionPlan().size());
93     for (TypedRange<Void> range : plan.getOldFileUncompressionPlan()) {
94       dataOut.writeLong(range.getOffset());
95       dataOut.writeLong(range.getLength());
96     }
97 
98     // Write out all the delta-friendly new file recompression instructions
99     dataOut.writeInt(plan.getDeltaFriendlyNewFileRecompressionPlan().size());
100     for (TypedRange<JreDeflateParameters> range : plan.getDeltaFriendlyNewFileRecompressionPlan()) {
101       dataOut.writeLong(range.getOffset());
102       dataOut.writeLong(range.getLength());
103       // Write the deflate information
104       dataOut.write(PatchConstants.CompatibilityWindowId.DEFAULT_DEFLATE.patchValue);
105       dataOut.write(range.getMetadata().level);
106       dataOut.write(range.getMetadata().strategy);
107       dataOut.write(range.getMetadata().nowrap ? 1 : 0);
108     }
109 
110     // Now the delta section
111     // First write the number of deltas present in the patch. In v1, there is always exactly one
112     // delta, and it is for the entire input; in future versions there may be multiple deltas, of
113     // arbitrary types.
114     dataOut.writeInt(1);
115     // In v1 the delta format is always bsdiff, so write it unconditionally.
116     dataOut.write(PatchConstants.DeltaFormat.BSDIFF.patchValue);
117 
118     // Write the working ranges. In v1 these are always the entire contents of the delta-friendly
119     // old file and the delta-friendly new file. These are for forward compatibility with future
120     // versions that may allow deltas of arbitrary formats to be mapped to arbitrary ranges.
121     dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly old file
122     dataOut.writeLong(deltaFriendlyOldFileSize); // i.e., length of the working range in old
123     dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly new file
124     dataOut.writeLong(deltaFriendlyNewFileSize); // i.e., length of the working range in new
125 
126     // Finally, the length of the delta and the delta itself.
127     dataOut.writeLong(deltaFile.length());
128     try (FileInputStream deltaFileIn = new FileInputStream(deltaFile);
129         BufferedInputStream deltaIn = new BufferedInputStream(deltaFileIn)) {
130       byte[] buffer = new byte[32768];
131       int numRead = 0;
132       while ((numRead = deltaIn.read(buffer)) >= 0) {
133         dataOut.write(buffer, 0, numRead);
134       }
135     }
136     dataOut.flush();
137   }
138 }
139