aboutsummaryrefslogtreecommitdiff
path: root/generator/src/main/java/com/google/archivepatcher/generator/FileByFileV1DeltaGenerator.java
blob: c704811abcbf091db83a9874a14c5538f23ca0ea (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 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.generator.bsdiff.BsDiffDeltaGenerator;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Generates file-by-file patches.
 */
public class FileByFileV1DeltaGenerator implements DeltaGenerator {

  /** Optional modifiers for planning and patch generation. */
  private final List<RecommendationModifier> recommendationModifiers;

  /**
   * Constructs a new generator for File-by-File v1 patches, using the specified configuration.
   *
   * @param recommendationModifiers optionally, {@link RecommendationModifier}s to use for modifying
   *     the planning phase of patch generation. These can be used to, e.g., limit the total amount
   *     of recompression that a patch applier needs to do. Modifiers are applied in the order they
   *     are specified.
   */
  public FileByFileV1DeltaGenerator(RecommendationModifier... recommendationModifiers) {
    if (recommendationModifiers != null) {
      this.recommendationModifiers =
          Collections.unmodifiableList(Arrays.asList(recommendationModifiers));
    } else {
      this.recommendationModifiers = Collections.emptyList();
    }
  }

  /**
   * Generate a V1 patch for the specified input files and write the patch to the specified {@link
   * OutputStream}. The written patch is <em>raw</em>, i.e. it has not been compressed. Compression
   * should almost always be applied to the patch, either right in the specified {@link
   * OutputStream} or in a post-processing step, prior to transmitting the patch to the patch
   * applier.
   *
   * @param oldFile the original old file to read (will not be modified)
   * @param newFile the original new file to read (will not be modified)
   * @param patchOut the stream to write the patch to
   * @throws IOException if unable to complete the operation due to an I/O error
   * @throws InterruptedException if any thread has interrupted the current thread
   */
  @Override
  public void generateDelta(File oldFile, File newFile, OutputStream patchOut)
      throws IOException, InterruptedException {
    try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder();
        TempFileHolder deltaFriendlyNewFile = new TempFileHolder();
        TempFileHolder deltaFile = new TempFileHolder();
        FileOutputStream deltaFileOut = new FileOutputStream(deltaFile.file);
        BufferedOutputStream bufferedDeltaOut = new BufferedOutputStream(deltaFileOut)) {
      PreDiffExecutor.Builder builder =
          new PreDiffExecutor.Builder()
              .readingOriginalFiles(oldFile, newFile)
              .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file);
      for (RecommendationModifier modifier : recommendationModifiers) {
        builder.withRecommendationModifier(modifier);
      }
      PreDiffExecutor executor = builder.build();
      PreDiffPlan preDiffPlan = executor.prepareForDiffing();
      DeltaGenerator deltaGenerator = getDeltaGenerator();
      deltaGenerator.generateDelta(
          deltaFriendlyOldFile.file, deltaFriendlyNewFile.file, bufferedDeltaOut);
      bufferedDeltaOut.close();
      PatchWriter patchWriter =
          new PatchWriter(
              preDiffPlan,
              deltaFriendlyOldFile.file.length(),
              deltaFriendlyNewFile.file.length(),
              deltaFile.file);
      patchWriter.writeV1Patch(patchOut);
    }
  }

  /**
   * Generate a V1 patch pre diffing plan.
   *
   * @param oldFile the original old file to read (will not be modified)
   * @param newFile the original new file to read (will not be modified)
   * @return the plan
   * @throws IOException if unable to complete the operation due to an I/O error
   * @throws InterruptedException if any thread has interrupted the current thread
   */
  public PreDiffPlan generatePreDiffPlan(File oldFile, File newFile)
      throws IOException, InterruptedException {
    try (TempFileHolder deltaFriendlyOldFile = new TempFileHolder();
        TempFileHolder deltaFriendlyNewFile = new TempFileHolder()) {
      PreDiffExecutor.Builder builder =
          new PreDiffExecutor.Builder()
              .readingOriginalFiles(oldFile, newFile)
              .writingDeltaFriendlyFiles(deltaFriendlyOldFile.file, deltaFriendlyNewFile.file);
      for (RecommendationModifier modifier : recommendationModifiers) {
        builder.withRecommendationModifier(modifier);
      }

      PreDiffExecutor executor = builder.build();

      return executor.prepareForDiffing();
    }
  }

  // Visible for testing only
  protected DeltaGenerator getDeltaGenerator() {
    return new BsDiffDeltaGenerator();
  }
}