2 files added
1 files modified
| New file |
| | |
| | | package de.micromata.borgbutler; |
| | | |
| | | import de.micromata.borgbutler.json.borg.BorgFilesystemItem; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Iterator; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Extracts the differences between two archives of one repo. |
| | | */ |
| | | public class DiffTool { |
| | | private static Logger log = LoggerFactory.getLogger(DiffTool.class); |
| | | |
| | | /** |
| | | * @param items Sorted list of items from the current archive. |
| | | * @param otherItems Sorted list of items of the archive to extract differences. |
| | | * @return A list of differing items (new, removed and modified ones). |
| | | */ |
| | | public static List<BorgFilesystemItem> extractDifferences(List<BorgFilesystemItem> items, List<BorgFilesystemItem> otherItems) { |
| | | List<BorgFilesystemItem> currentList = items != null ? items : new ArrayList<>(); |
| | | List<BorgFilesystemItem> otherList = otherItems != null ? otherItems : new ArrayList<>(); |
| | | List<BorgFilesystemItem> result = new ArrayList<>(); |
| | | Iterator<BorgFilesystemItem> currentIt = currentList.iterator(); |
| | | Iterator<BorgFilesystemItem> otherIt = otherList.iterator(); |
| | | BorgFilesystemItem current = null; |
| | | BorgFilesystemItem other = null; |
| | | while (true) { |
| | | if (current == null && currentIt.hasNext()) |
| | | current = currentIt.next(); |
| | | if (other == null && otherIt.hasNext()) |
| | | other = otherIt.next(); |
| | | if (current == null || other == null) { |
| | | break; |
| | | } |
| | | int cmp = current.compareTo(other); |
| | | if (cmp == 0) { // Items represents both the same file system item. |
| | | if (current.equals(other)) { |
| | | current = other = null; // increment both iterators. |
| | | continue; |
| | | } |
| | | // Current entry differs: |
| | | current.setDiffStatus(BorgFilesystemItem.DiffStatus.MODIFIED); |
| | | current.setDiffItem(other); |
| | | current.buildDifferencesString(); |
| | | result.add(current); |
| | | current = other = null; // increment both iterators. |
| | | } else if (cmp < 0) { |
| | | result.add(current.setDiffStatus(BorgFilesystemItem.DiffStatus.NEW)); |
| | | current = currentIt.hasNext() ? currentIt.next() : null; |
| | | } else { |
| | | result.add(other.setDiffStatus(BorgFilesystemItem.DiffStatus.REMOVED)); |
| | | other = otherIt.hasNext() ? otherIt.next() : null; |
| | | } |
| | | } |
| | | while (currentIt.hasNext()) { |
| | | if (current == null) |
| | | current = currentIt.next(); |
| | | result.add(current.setDiffStatus(BorgFilesystemItem.DiffStatus.NEW)); |
| | | current = null; |
| | | } |
| | | while (otherIt.hasNext()) { |
| | | if (other == null) |
| | | other = otherIt.next(); |
| | | result.add(other.setDiffStatus(BorgFilesystemItem.DiffStatus.REMOVED)); |
| | | other = null; |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | |
| | | package de.micromata.borgbutler.json.borg; |
| | | |
| | | import lombok.AccessLevel; |
| | | import lombok.Getter; |
| | | import lombok.Setter; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.apache.commons.lang3.builder.EqualsBuilder; |
| | | import org.apache.commons.lang3.builder.HashCodeBuilder; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | |
| | | import java.io.Serializable; |
| | | |
| | | public class BorgFilesystemItem implements Serializable, Comparable<BorgFilesystemItem> { |
| | | private transient static Logger log = LoggerFactory.getLogger(BorgFilesystemItem.class); |
| | | private static final long serialVersionUID = -5545350851640655468L; |
| | | |
| | | /** |
| | | * If running in diff mode, this flag specifies the type of difference. Null represents unmodified. |
| | | */ |
| | | public enum DiffStatus {NEW, REMOVED, MODIFIED} |
| | | |
| | | /** |
| | | * d (directory), - (file) |
| | | */ |
| | |
| | | * Unix mode, e. g. <tt>drwxr-xr-x</tt> |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | private String mode; |
| | | @Getter |
| | | private String user; |
| | |
| | | @Setter |
| | | private String mtime; |
| | | @Getter |
| | | @Setter |
| | | private long size; |
| | | /** |
| | | * Represents the number of the file in the archive (for downloading). This field is created and only known by BorgButler. |
| | |
| | | * This String may used for displaying. |
| | | */ |
| | | @Getter |
| | | @Setter |
| | | private String differences; |
| | | |
| | | @Override |
| | |
| | | return -1; |
| | | } |
| | | if (o.path == null) { |
| | | return 1; |
| | | return 1; |
| | | } |
| | | return path.compareToIgnoreCase(o.path); |
| | | } |
| | | |
| | | @Override |
| | | public boolean equals(Object obj) { |
| | | if (obj == null) { |
| | | return false; |
| | | } |
| | | if (obj == this) { |
| | | return true; |
| | | } |
| | | if (obj.getClass() != getClass()) { |
| | | return false; |
| | | } |
| | | BorgFilesystemItem rhs = (BorgFilesystemItem) obj; |
| | | return new EqualsBuilder() |
| | | .append(path, rhs.path) |
| | | .append(type, rhs.type) |
| | | .append(mode, rhs.mode) |
| | | .append(user, rhs.user) |
| | | .append(group, rhs.group) |
| | | .append(uid, rhs.uid) |
| | | .append(gid, rhs.gid) |
| | | .append(mtime, rhs.mtime) |
| | | .append(size, rhs.size) |
| | | .append(flags, rhs.flags) |
| | | .isEquals(); |
| | | } |
| | | |
| | | @Override |
| | | public int hashCode() { |
| | | return new HashCodeBuilder() |
| | | .append(path) |
| | | .append(type) |
| | | .append(mode) |
| | | .append(user) |
| | | .append(group) |
| | | .append(uid) |
| | | .append(gid) |
| | | .append(mtime) |
| | | .append(size) |
| | | .append(flags) |
| | | .toHashCode(); |
| | | } |
| | | |
| | | /** |
| | | * Compares all fields and creates human readable string with differences. |
| | | */ |
| | | public void buildDifferencesString() { |
| | | if (diffItem == null) { |
| | | // Nothing to do. |
| | | return; |
| | | } |
| | | if (!StringUtils.equals(this.path, diffItem.path)) { |
| | | log.error("*** Internal error: Differences should only be made on same path object: current='" + path + "', other='" + diffItem.path + "'."); |
| | | return; |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | appendDiff(sb, "type", this.type, diffItem.type); |
| | | appendDiff(sb, "mode", this.mode, diffItem.mode); |
| | | appendDiff(sb, "user", this.user, diffItem.user); |
| | | appendDiff(sb, "group", this.group, diffItem.group); |
| | | appendDiff(sb, "uid", this.uid, diffItem.uid); |
| | | appendDiff(sb, "gid", this.gid, diffItem.gid); |
| | | appendDiff(sb, "mtime", this.mtime, diffItem.mtime); |
| | | appendDiff(sb, "size", this.size, diffItem.size); |
| | | if (sb.length() > 0) { |
| | | diffStatus = DiffStatus.MODIFIED; |
| | | this.differences = sb.toString(); |
| | | } |
| | | } |
| | | |
| | | private void appendDiff(StringBuilder sb, String field, String current, String other) { |
| | | if (StringUtils.equals(current, other)) { |
| | | // Not modified. |
| | | return; |
| | | } |
| | | if (sb.length() > 0) { |
| | | sb.append(", "); |
| | | } |
| | | sb.append(field + ":['" + other + "'->'" + current + "']"); |
| | | } |
| | | |
| | | private void appendDiff(StringBuilder sb, String field, long current, long other) { |
| | | if (current == other) { |
| | | // Not modified. |
| | | return; |
| | | } |
| | | if (sb.length() > 0) { |
| | | sb.append(", "); |
| | | } |
| | | sb.append(field + ": ['" + current + "' -> '" + other + "']"); |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return path; |
| | | } |
| | | } |
| New file |
| | |
| | | package de.micromata.borgbutler; |
| | | |
| | | import de.micromata.borgbutler.json.borg.BorgFilesystemItem; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | |
| | | public class DiffToolTest { |
| | | @Test |
| | | void differencesTest() { |
| | | BorgFilesystemItem i1 = create("etc", true, "drwx------", 0, "2018-11-21"); |
| | | BorgFilesystemItem i2 = create("etc", true, "drwx------", 0, "2018-11-21"); |
| | | assertTrue(i1.equals(i2)); |
| | | i1.setType("-").setMode("drwxrwxrwx").setMtime("2018-11-22"); |
| | | assertFalse(i1.equals(i2)); |
| | | i1.setDiffItem(i2).buildDifferencesString(); |
| | | assertEquals("type:['d'->'-'], mode:['drwx------'->'drwxrwxrwx'], mtime:['2018-11-21'->'2018-11-22']", i1.getDifferences()); |
| | | } |
| | | |
| | | @Test |
| | | void diffToolTest() { |
| | | List<BorgFilesystemItem> l1 = null; |
| | | List<BorgFilesystemItem> l2 = null; |
| | | List<BorgFilesystemItem> result; |
| | | assertEquals(0, DiffTool.extractDifferences(l1, l2).size()); |
| | | l1 = create(); |
| | | result = DiffTool.extractDifferences(l1, l2); |
| | | assertEquals(7, result.size()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus()); |
| | | result = DiffTool.extractDifferences(l2, l1); |
| | | assertEquals(7, result.size()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus()); |
| | | |
| | | l1 = create(); |
| | | l2 = create(); |
| | | result = DiffTool.extractDifferences(l2, l1); |
| | | assertEquals(0, result.size()); |
| | | remove(l2, "etc"); // 0 |
| | | remove(l2, "etc/passwd"); // 1 |
| | | remove(l1, "home/kai/.borgbutler/borgbutler-config-bak.json"); // 2 |
| | | get(l1, "home/kai/.borgbutler/borgbutler-config.json").setSize(712).setMtime("2018-11-22"); // 3 |
| | | result = DiffTool.extractDifferences(l1, l2); |
| | | assertEquals(4, result.size()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(2).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.MODIFIED, result.get(3).getDiffStatus()); |
| | | |
| | | result = DiffTool.extractDifferences(l2, l1); |
| | | assertEquals(4, result.size()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(2).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.MODIFIED, result.get(3).getDiffStatus()); |
| | | |
| | | l1 = create(); |
| | | l2 = create(); |
| | | remove(l2, "etc"); // 0 |
| | | remove(l2, "etc/passwd"); // 1 |
| | | remove(l1, "home/kai/.borgbutler/borgbutler-config.json"); // 2 |
| | | result = DiffTool.extractDifferences(l1, l2); |
| | | assertEquals(3, result.size()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(2).getDiffStatus()); |
| | | result = DiffTool.extractDifferences(l2, l1); |
| | | assertEquals(3, result.size()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus()); |
| | | assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(2).getDiffStatus()); |
| | | } |
| | | |
| | | private BorgFilesystemItem create(String path, boolean directory, String mode, long size, String mtime) { |
| | | return new BorgFilesystemItem() |
| | | .setPath(path) |
| | | .setType(directory ? "d" : "-") |
| | | .setMode(mode) |
| | | .setSize(size) |
| | | .setMtime(mtime); |
| | | } |
| | | |
| | | private List<BorgFilesystemItem> create() { |
| | | List<BorgFilesystemItem> list = new ArrayList<>(); |
| | | list.add(create("etc", true, "drwx------", 0, "2018-11-21")); |
| | | list.add(create("etc/passwd", false, "-rwx------", 100, "2018-11-21")); |
| | | list.add(create("home", true, "drwx------", 0, "2018-11-21")); |
| | | list.add(create("home/kai", true, "-rwx------", 0, "2018-11-21")); |
| | | list.add(create("home/kai/.borgbutler", true, "-rwx------", 0, "2018-11-21")); |
| | | list.add(create("home/kai/.borgbutler/borgbutler-config-bak.json", false, "drwxr-xr-x", 666, "2018-11-19")); |
| | | list.add(create("home/kai/.borgbutler/borgbutler-config.json", false, "drwxr-xr-x", 666, "2018-11-21")); |
| | | Collections.sort(list); |
| | | return list; |
| | | } |
| | | |
| | | private void remove(List<BorgFilesystemItem> list, String path) { |
| | | BorgFilesystemItem item = get(list, path); |
| | | list.remove(item); |
| | | } |
| | | |
| | | private BorgFilesystemItem get(List<BorgFilesystemItem> list, String path) { |
| | | for (BorgFilesystemItem item : list) { |
| | | if (item.getPath().equals(path)) { |
| | | return item; |
| | | } |
| | | } |
| | | fail(); |
| | | return null; |
| | | } |
| | | } |