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