blob: d6e055f95a9c3b3d152035591531d297d724e47c [file] [log] [blame]
// 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.generator;
import com.google.archivepatcher.shared.JreDeflateParameters;
import com.google.archivepatcher.shared.PatchConstants;
import com.google.archivepatcher.shared.TypedRange;
import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Writes patches.
*/
public class PatchWriter {
/**
* The patch plan.
*/
private final PreDiffPlan plan;
/**
* The expected size of the delta-friendly old file, provided as a convenience for the patch
* <strong>applier</strong> to reserve space on the filesystem for applying the patch.
*/
private final long deltaFriendlyOldFileSize;
/**
* The expected size of the delta-friendly new file, provided for forward compatibility.
*/
private final long deltaFriendlyNewFileSize;
/**
* The delta that transforms the old delta-friendly file into the new delta-friendly file.
*/
private final File deltaFile;
/**
* Creates a new patch writer.
*
* @param plan the patch plan
* @param deltaFriendlyOldFileSize the expected size of the delta-friendly old file, provided as a
* convenience for the patch <strong>applier</strong> to reserve space on the filesystem for
* applying the patch
* @param deltaFriendlyNewFileSize the expected size of the delta-friendly new file, provided for
* forward compatibility
* @param deltaFile the delta that transforms the old delta-friendly file into the new
* delta-friendly file
*/
public PatchWriter(
PreDiffPlan plan,
long deltaFriendlyOldFileSize,
long deltaFriendlyNewFileSize,
File deltaFile) {
this.plan = plan;
this.deltaFriendlyOldFileSize = deltaFriendlyOldFileSize;
this.deltaFriendlyNewFileSize = deltaFriendlyNewFileSize;
this.deltaFile = deltaFile;
}
/**
* Write a v1-style patch to the specified output stream.
* @param out the stream to write the patch to
* @throws IOException if anything goes wrong
*/
public void writeV1Patch(OutputStream out) throws IOException {
// Use DataOutputStream for ease of writing. This is deliberately left open, as closing it would
// close the output stream that was passed in and that is not part of the method's documented
// behavior.
@SuppressWarnings("resource")
DataOutputStream dataOut = new DataOutputStream(out);
dataOut.write(PatchConstants.IDENTIFIER.getBytes("US-ASCII"));
dataOut.writeInt(0); // Flags (reserved)
dataOut.writeLong(deltaFriendlyOldFileSize);
// Write out all the delta-friendly old file uncompression instructions
dataOut.writeInt(plan.getOldFileUncompressionPlan().size());
for (TypedRange<Void> range : plan.getOldFileUncompressionPlan()) {
dataOut.writeLong(range.getOffset());
dataOut.writeLong(range.getLength());
}
// Write out all the delta-friendly new file recompression instructions
dataOut.writeInt(plan.getDeltaFriendlyNewFileRecompressionPlan().size());
for (TypedRange<JreDeflateParameters> range : plan.getDeltaFriendlyNewFileRecompressionPlan()) {
dataOut.writeLong(range.getOffset());
dataOut.writeLong(range.getLength());
// Write the deflate information
dataOut.write(PatchConstants.CompatibilityWindowId.DEFAULT_DEFLATE.patchValue);
dataOut.write(range.getMetadata().level);
dataOut.write(range.getMetadata().strategy);
dataOut.write(range.getMetadata().nowrap ? 1 : 0);
}
// Now the delta section
// First write the number of deltas present in the patch. In v1, there is always exactly one
// delta, and it is for the entire input; in future versions there may be multiple deltas, of
// arbitrary types.
dataOut.writeInt(1);
// In v1 the delta format is always bsdiff, so write it unconditionally.
dataOut.write(PatchConstants.DeltaFormat.BSDIFF.patchValue);
// Write the working ranges. In v1 these are always the entire contents of the delta-friendly
// old file and the delta-friendly new file. These are for forward compatibility with future
// versions that may allow deltas of arbitrary formats to be mapped to arbitrary ranges.
dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly old file
dataOut.writeLong(deltaFriendlyOldFileSize); // i.e., length of the working range in old
dataOut.writeLong(0); // i.e., start of the working range in the delta-friendly new file
dataOut.writeLong(deltaFriendlyNewFileSize); // i.e., length of the working range in new
// Finally, the length of the delta and the delta itself.
dataOut.writeLong(deltaFile.length());
try (FileInputStream deltaFileIn = new FileInputStream(deltaFile);
BufferedInputStream deltaIn = new BufferedInputStream(deltaFileIn)) {
byte[] buffer = new byte[32768];
int numRead = 0;
while ((numRead = deltaIn.read(buffer)) >= 0) {
dataOut.write(buffer, 0, numRead);
}
}
dataOut.flush();
}
}