• 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.shared;
16 
17 import java.io.File;
18 import java.io.IOException;
19 import java.io.OutputStream;
20 import java.util.ArrayList;
21 import java.util.List;
22 
23 /**
24  * Utilities for generating delta-friendly files.
25  */
26 public class DeltaFriendlyFile {
27 
28   /**
29    * The default size of the copy buffer to use for copying between streams.
30    */
31   public static final int DEFAULT_COPY_BUFFER_SIZE = 32768;
32 
33   /**
34    * Invoke {@link #generateDeltaFriendlyFile(List, File, OutputStream, boolean, int)} with
35    * <code>generateInverse</code> set to <code>true</code> and a copy buffer size of
36    * {@link #DEFAULT_COPY_BUFFER_SIZE}.
37    * @param rangesToUncompress the ranges to be uncompressed during transformation to a
38    * delta-friendly form
39    * @param file the file to read from
40    * @param deltaFriendlyOut a stream to write the delta-friendly file to
41    * @return the ranges in the delta-friendly file that correspond to the ranges in the original
42    * file, with identical metadata and in the same order
43    * @throws IOException if anything goes wrong
44    */
generateDeltaFriendlyFile( List<TypedRange<T>> rangesToUncompress, File file, OutputStream deltaFriendlyOut)45   public static <T> List<TypedRange<T>> generateDeltaFriendlyFile(
46       List<TypedRange<T>> rangesToUncompress, File file, OutputStream deltaFriendlyOut)
47       throws IOException {
48     return generateDeltaFriendlyFile(
49         rangesToUncompress, file, deltaFriendlyOut, true, DEFAULT_COPY_BUFFER_SIZE);
50   }
51 
52   /**
53    * Generate one delta-friendly file and (optionally) return the ranges necessary to invert the
54    * transform, in file order. There is a 1:1 correspondence between the ranges in the input
55    * list and the returned list, but the offsets and lengths will be different (the input list
56    * represents compressed data, the output list represents uncompressed data). The ability to
57    * suppress generation of the inverse range and to specify the size of the copy buffer are
58    * provided for clients that desire a minimal memory footprint.
59    * @param rangesToUncompress the ranges to be uncompressed during transformation to a
60    * delta-friendly form
61    * @param file the file to read from
62    * @param deltaFriendlyOut a stream to write the delta-friendly file to
63    * @param generateInverse if <code>true</code>, generate and return a list of inverse ranges in
64    * file order; otherwise, do all the normal work but return null instead of the inverse ranges
65    * @param copyBufferSize the size of the buffer to use for copying bytes between streams
66    * @return if <code>generateInverse</code> was true, returns the ranges in the delta-friendly file
67    * that correspond to the ranges in the original file, with identical metadata and in the same
68    * order; otherwise, return null
69    * @throws IOException if anything goes wrong
70    */
generateDeltaFriendlyFile( List<TypedRange<T>> rangesToUncompress, File file, OutputStream deltaFriendlyOut, boolean generateInverse, int copyBufferSize)71   public static <T> List<TypedRange<T>> generateDeltaFriendlyFile(
72       List<TypedRange<T>> rangesToUncompress,
73       File file,
74       OutputStream deltaFriendlyOut,
75       boolean generateInverse,
76       int copyBufferSize)
77       throws IOException {
78     List<TypedRange<T>> inverseRanges = null;
79     if (generateInverse) {
80       inverseRanges = new ArrayList<TypedRange<T>>(rangesToUncompress.size());
81     }
82     long lastReadOffset = 0;
83     RandomAccessFileInputStream oldFileRafis = null;
84     PartiallyUncompressingPipe filteredOut =
85         new PartiallyUncompressingPipe(deltaFriendlyOut, copyBufferSize);
86     try {
87       oldFileRafis = new RandomAccessFileInputStream(file);
88       for (TypedRange<T> rangeToUncompress : rangesToUncompress) {
89         long gap = rangeToUncompress.getOffset() - lastReadOffset;
90         if (gap > 0) {
91           // Copy bytes up to the range start point
92           oldFileRafis.setRange(lastReadOffset, gap);
93           filteredOut.pipe(oldFileRafis, PartiallyUncompressingPipe.Mode.COPY);
94         }
95 
96         // Now uncompress the range.
97         oldFileRafis.setRange(rangeToUncompress.getOffset(), rangeToUncompress.getLength());
98         long inverseRangeStart = filteredOut.getNumBytesWritten();
99         // TODO(andrewhayden): Support nowrap=false here? Never encountered in practice.
100         // This would involve catching the ZipException, checking if numBytesWritten is still zero,
101         // resetting the stream and trying again.
102         filteredOut.pipe(oldFileRafis, PartiallyUncompressingPipe.Mode.UNCOMPRESS_NOWRAP);
103         lastReadOffset = rangeToUncompress.getOffset() + rangeToUncompress.getLength();
104 
105         if (generateInverse) {
106           long inverseRangeEnd = filteredOut.getNumBytesWritten();
107           long inverseRangeLength = inverseRangeEnd - inverseRangeStart;
108           TypedRange<T> inverseRange =
109               new TypedRange<T>(
110                   inverseRangeStart, inverseRangeLength, rangeToUncompress.getMetadata());
111           inverseRanges.add(inverseRange);
112         }
113       }
114       // Finish the final bytes of the file
115       long bytesLeft = oldFileRafis.length() - lastReadOffset;
116       if (bytesLeft > 0) {
117         oldFileRafis.setRange(lastReadOffset, bytesLeft);
118         filteredOut.pipe(oldFileRafis, PartiallyUncompressingPipe.Mode.COPY);
119       }
120     } finally {
121       try {
122         oldFileRafis.close();
123       } catch (Exception ignored) {
124         // Nothing
125       }
126       try {
127         filteredOut.close();
128       } catch (Exception ignored) {
129         // Nothing
130       }
131     }
132     return inverseRanges;
133   }
134 }
135