diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java index b4cc9296..d181a53c 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java @@ -23,6 +23,7 @@ public class ArbitraryDataCombiner { private Path pathAfter; private byte[] signatureBefore; private Path finalPath; + ArbitraryDataMetadataPatch metadata; public ArbitraryDataCombiner(Path pathBefore, Path pathAfter, byte[] signatureBefore) { this.pathBefore = pathBefore; @@ -33,6 +34,7 @@ public class ArbitraryDataCombiner { public void combine() throws IOException { try { this.preExecute(); + this.readMetadata(); this.validatePreviousSignature(); this.validatePreviousHash(); this.process(); @@ -86,14 +88,17 @@ public class ArbitraryDataCombiner { } - private void validatePreviousSignature() throws IOException { + private void readMetadata() throws IOException { + this.metadata = new ArbitraryDataMetadataPatch(this.pathAfter); + this.metadata.read(); + } + + private void validatePreviousSignature() { if (this.signatureBefore == null) { throw new IllegalStateException("No previous signature passed to the combiner"); } - ArbitraryDataMetadataPatch metadata = new ArbitraryDataMetadataPatch(this.pathAfter); - metadata.read(); - byte[] previousSignature = metadata.getPreviousSignature(); + byte[] previousSignature = this.metadata.getPreviousSignature(); if (previousSignature == null) { throw new IllegalStateException("Unable to extract previous signature from patch metadata"); } @@ -105,9 +110,7 @@ public class ArbitraryDataCombiner { } private void validatePreviousHash() throws IOException { - ArbitraryDataMetadataPatch metadata = new ArbitraryDataMetadataPatch(this.pathAfter); - metadata.read(); - byte[] previousHash = metadata.getPreviousHash(); + byte[] previousHash = this.metadata.getPreviousHash(); if (previousHash == null) { throw new IllegalStateException("Unable to extract previous hash from patch metadata"); } @@ -123,7 +126,8 @@ public class ArbitraryDataCombiner { } private void process() throws IOException { - ArbitraryDataMerge merge = new ArbitraryDataMerge(this.pathBefore, this.pathAfter); + String patchType = metadata.getPatchType(); + ArbitraryDataMerge merge = new ArbitraryDataMerge(this.pathBefore, this.pathAfter, metadata.getPatchType()); merge.compute(); this.finalPath = merge.getMergePath(); } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java index d4faf124..6a1e6b20 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java @@ -1,16 +1,25 @@ package org.qortal.arbitrary; +import com.github.difflib.DiffUtils; +import com.github.difflib.UnifiedDiffUtils; +import com.github.difflib.patch.Patch; +import com.github.difflib.patch.PatchFailedException; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.eclipse.jetty.util.IO; import org.qortal.arbitrary.metadata.ArbitraryDataMetadataPatch; import org.qortal.settings.Settings; import org.qortal.utils.FilesystemUtils; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.List; +import java.util.Objects; import java.util.UUID; public class ArbitraryDataMerge { @@ -19,13 +28,15 @@ public class ArbitraryDataMerge { private Path pathBefore; private Path pathAfter; + private String patchType; private Path mergePath; private String identifier; private ArbitraryDataMetadataPatch metadata; - public ArbitraryDataMerge(Path pathBefore, Path pathAfter) { + public ArbitraryDataMerge(Path pathBefore, Path pathAfter, String patchType) { this.pathBefore = pathBefore; this.pathAfter = pathAfter; + this.patchType = patchType; } public void compute() throws IOException { @@ -87,8 +98,7 @@ public class ArbitraryDataMerge { List modifiedPaths = this.metadata.getModifiedPaths(); for (Path path : modifiedPaths) { LOGGER.info("File was modified: {}", path.toString()); - Path filePath = Paths.get(this.pathAfter.toString(), path.toString()); - ArbitraryDataMerge.copyPathToBaseDir(filePath, this.mergePath, path); + this.applyPatch(path); } List removedPaths = this.metadata.getRemovedPaths(); @@ -98,6 +108,60 @@ public class ArbitraryDataMerge { } } + private void applyPatch(Path path) throws IOException { + if (Objects.equals(this.patchType, "unified-diff")) { + // Create destination file from patch + this.applyUnifiedDiffPatch(path); + } + else { + // Copy complete file + Path filePath = Paths.get(this.pathAfter.toString(), path.toString()); + ArbitraryDataMerge.copyPathToBaseDir(filePath, this.mergePath, path); + } + } + + private void applyUnifiedDiffPatch(Path path) throws IOException { + Path originalPath = Paths.get(this.pathBefore.toString(), path.toString()); + Path patchPath = Paths.get(this.pathAfter.toString(), path.toString()); + Path mergePath = Paths.get(this.mergePath.toString(), path.toString()); + + if (!patchPath.toFile().exists()) { + // Patch file doesn't exist, but its path was included in modifiedPaths + // TODO: We ought to throw an exception here, but skipping for now + return; + } + + // Delete an existing file, as we are starting from a duplicate of pathBefore + File destFile = mergePath.toFile(); + if (destFile.exists() && destFile.isFile()) { + Files.delete(mergePath); + } + + List originalContents = FileUtils.readLines(originalPath.toFile(), StandardCharsets.UTF_8); + List patchContents = FileUtils.readLines(patchPath.toFile(), StandardCharsets.UTF_8); + + // At first, parse the unified diff file and get the patch + Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patchContents); + + // Then apply the computed patch to the given text + try { + List patchedContents = DiffUtils.patch(originalContents, patch); + + // Write the patched file to the merge directory + FileWriter fileWriter = new FileWriter(mergePath.toString(), true); + BufferedWriter writer = new BufferedWriter(fileWriter); + for (String line : patchedContents) { + writer.append(line); + writer.newLine(); + } + writer.flush(); + writer.close(); + + } catch (PatchFailedException e) { + throw new IllegalStateException(String.format("Failed to apply patch for path %s: %s", path, e.getMessage())); + } + } + private void copyMetadata() throws IOException { Path filePath = Paths.get(this.pathAfter.toString(), ".qortal"); ArbitraryDataMerge.copyPathToBaseDir(filePath, this.mergePath, Paths.get(".qortal"));