mirror of https://github.com/micromata/borgbackup-butler.git

Kai Reinhard
28.10.2018 34de8e3ff4872c7e48e78d1fa102aa029ac2b261
It's a long way, but is it also useful?
3 files added
12 files modified
783 ■■■■ changed files
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java 5 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/DiffTool.java 28 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ArchiveFilelistCache.java 22 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java 26 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/Filesystem.java 25 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/FilesystemItem.java 187 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/data/FileSystemFilter.java 151 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgFilesystemItem.java 160 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/DiffToolTest.java 84 ●●●● patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/ArchiveFilelistCacheTest.java 21 ●●●● patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java 3 ●●●● patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/FilesystemTest.java 34 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/data/FileSystemFilterTest.java 21 ●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java 6 ●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java 10 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
@@ -1,5 +1,6 @@
package de.micromata.borgbutler;
import de.micromata.borgbutler.cache.FilesystemItem;
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.data.Repository;
@@ -16,7 +17,6 @@
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
@@ -178,12 +178,11 @@
        try (Scanner scanner = new Scanner(command.getResponse())) {
            while (scanner.hasNextLine()) {
                String json = scanner.nextLine();
                BorgFilesystemItem item = JsonUtils.fromJson(BorgFilesystemItem.class, json);
                FilesystemItem item = JsonUtils.fromJson(FilesystemItem.class, json);
                item.setMtime(DateUtils.format(item.getMtime()));
                content.add(item);
            }
        }
        Collections.sort(content); // Sort by path.
        return content;
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/DiffTool.java
@@ -1,6 +1,6 @@
package de.micromata.borgbutler;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import de.micromata.borgbutler.cache.FilesystemItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,14 +19,14 @@
     * @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;
    public static List<FilesystemItem> extractDifferences(List<FilesystemItem> items, List<FilesystemItem> otherItems) {
        List<FilesystemItem> currentList = items != null ? items : new ArrayList<>();
        List<FilesystemItem> otherList = otherItems != null ? otherItems : new ArrayList<>();
        List<FilesystemItem> result = new ArrayList<>();
        Iterator<FilesystemItem> currentIt = currentList.iterator();
        Iterator<FilesystemItem> otherIt = otherList.iterator();
        FilesystemItem current = null;
        FilesystemItem other = null;
        while (true) {
            if (current == null && currentIt.hasNext())
                current = currentIt.next();
@@ -42,25 +42,25 @@
                    continue;
                }
                // Current entry differs:
                current.setDiffStatus(BorgFilesystemItem.DiffStatus.MODIFIED);
                current.setDiffStatus(FilesystemItem.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));
                result.add(current.setDiffStatus(FilesystemItem.DiffStatus.NEW));
                current = currentIt.hasNext() ? currentIt.next() : null;
            } else {
                result.add(other.setDiffStatus(BorgFilesystemItem.DiffStatus.REMOVED));
                result.add(other.setDiffStatus(FilesystemItem.DiffStatus.REMOVED));
                other = otherIt.hasNext() ? otherIt.next() : null;
            }
        }
        while (current != null) {
            result.add(current.setDiffStatus(BorgFilesystemItem.DiffStatus.NEW));
            result.add(current.setDiffStatus(FilesystemItem.DiffStatus.NEW));
            current = currentIt.hasNext() ? currentIt.next() : null;
        }
        while (other != null) {
            result.add(other.setDiffStatus(BorgFilesystemItem.DiffStatus.REMOVED));
            result.add(other.setDiffStatus(FilesystemItem.DiffStatus.REMOVED));
            other = otherIt.hasNext() ? otherIt.next() : null;
        }
        return result;
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ArchiveFilelistCache.java
@@ -4,9 +4,7 @@
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.data.FileSystemFilter;
import de.micromata.borgbutler.data.Repository;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import de.micromata.borgbutler.utils.ReplaceUtils;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
@@ -31,7 +29,7 @@
    private int cacheArchiveContentMaxDiscSizeMB;
    private long FILES_EXPIRE_TIME = 7 * 24 * 3660 * 1000; // Expires after 7 days.
    public void save(BorgRepoConfig repoConfig, Archive archive, List<BorgFilesystemItem> filesystemItems) {
    public void save(BorgRepoConfig repoConfig, Archive archive, List<FilesystemItem> filesystemItems) {
        if (CollectionUtils.isEmpty(filesystemItems)) {
            return;
        }
@@ -39,7 +37,7 @@
        log.info("Saving archive content as file list: " + file.getAbsolutePath());
        try (ObjectOutputStream outputStream = new ObjectOutputStream(new BufferedOutputStream(new GzipCompressorOutputStream(new FileOutputStream(file))))) {
            outputStream.writeObject(filesystemItems.size());
            for (BorgFilesystemItem item : filesystemItems) {
            for (FilesystemItem item : filesystemItems) {
                outputStream.writeObject(item);
            }
            outputStream.writeObject("EOF");
@@ -66,7 +64,7 @@
     * @param archive
     * @return
     */
    public List<BorgFilesystemItem> load(BorgRepoConfig repoConfig, Archive archive) {
    public List<FilesystemItem> load(BorgRepoConfig repoConfig, Archive archive) {
        return load(repoConfig, archive, null);
    }
@@ -80,7 +78,7 @@
     * @param filter     If given, only file items matching this filter are returned.
     * @return
     */
    public List<BorgFilesystemItem> load(BorgRepoConfig repoConfig, Archive archive, FileSystemFilter filter) {
    public List<FilesystemItem> load(BorgRepoConfig repoConfig, Archive archive, FileSystemFilter filter) {
        File file = getFile(repoConfig, archive);
        if (!file.exists()) {
            return null;
@@ -93,13 +91,13 @@
     * @param filter If given, only file items matching this filter are returned.
     * @return
     */
    public List<BorgFilesystemItem> load(File file, FileSystemFilter filter) {
    public List<FilesystemItem> load(File file, FileSystemFilter filter) {
        if (!file.exists()) {
            log.error("File '" + file.getAbsolutePath() + "' doesn't exist. Can't get archive content files.");
            return null;
        }
        log.info("Loading archive content as file list from: " + file.getAbsolutePath());
        List<BorgFilesystemItem> list = null;
        List<FilesystemItem> list = null;
        try {
            // Set last modified time of file:
            Files.setAttribute(file.toPath(), "lastModifiedTime", FileTime.fromMillis(System.currentTimeMillis()));
@@ -118,9 +116,9 @@
            for (int i = 0; i < size; i++) {
                ++fileNumber;
                obj = inputStream.readObject();
                if (obj instanceof BorgFilesystemItem) {
                    BorgFilesystemItem item = (BorgFilesystemItem) obj;
                    item.setFileNumber(fileNumber);
                if (obj instanceof FilesystemItem) {
                    FilesystemItem item = (FilesystemItem) obj;
                   // item.setFileNumber(fileNumber);
                    if (filter == null || filter.matches(item)) {
                        list.add(item);
                        if (filter != null && filter.isFinished()) break;
@@ -138,7 +136,7 @@
        Collections.sort(list); // Sort by path (if archive list order wasn't correct).
        log.info("Loading done.");
        if (filter != null) {
            return filter.reduce(list);
           // return filter.reduce(list);
        }
        return list;
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -187,7 +187,7 @@
        return archive;
    }
    public List<BorgFilesystemItem> getArchiveContent(String archiveId) {
    public List<FilesystemItem> getArchiveContent(String archiveId) {
        return getArchiveContent(archiveId, true, null);
    }
@@ -232,7 +232,7 @@
     * @param filter    If not null, the result will be filtered.
     * @return
     */
    public List<BorgFilesystemItem> getArchiveContent(String archiveId, boolean forceLoad, FileSystemFilter filter) {
    public List<FilesystemItem> getArchiveContent(String archiveId, boolean forceLoad, FileSystemFilter filter) {
        Archive archive = getArchive(archiveId);
        if (archive == null) {
            log.error("Can't find archive with id '" + archiveId + "'. May-be it doesn't exist or the archives of the target repository aren't yet loaded.");
@@ -247,7 +247,7 @@
     * @param archive
     * @return
     */
    public List<BorgFilesystemItem> getArchiveContent(BorgRepoConfig repoConfig, Archive archive) {
    public List<FilesystemItem> getArchiveContent(BorgRepoConfig repoConfig, Archive archive) {
        return getArchiveContent(repoConfig, archive, true, null);
    }
@@ -257,28 +257,26 @@
     * @param filter     If given, only the items matching this filter are returned..
     * @return
     */
    public List<BorgFilesystemItem> getArchiveContent(BorgRepoConfig repoConfig, Archive archive, boolean forceLoad,
    public List<FilesystemItem> getArchiveContent(BorgRepoConfig repoConfig, Archive archive, boolean forceLoad,
                                                      FileSystemFilter filter) {
        if (archive == null || StringUtils.isBlank(archive.getName())) {
            return null;
        }
        List<BorgFilesystemItem> items = archiveFilelistCache.load(repoConfig, archive, filter);
        List<FilesystemItem> items = archiveFilelistCache.load(repoConfig, archive, filter);
        if (items == null && forceLoad) {
            List<BorgFilesystemItem> list = BorgCommands.listArchiveContent(repoConfig, archive);
            if (CollectionUtils.isNotEmpty(list)) {
                archiveFilelistCache.save(repoConfig, archive, list);
            List<BorgFilesystemItem> borgList = BorgCommands.listArchiveContent(repoConfig, archive);
            FilesystemItem root =  Filesystem.build(borgList);
            if (CollectionUtils.isNotEmpty(root.getChilds())) {
                archiveFilelistCache.save(repoConfig, archive, root.getChilds());
                items = new ArrayList<>();
                int fileNumber = -1;
                for (BorgFilesystemItem item : list) {
                    ++fileNumber;
                    item.setFileNumber(fileNumber);
                for (FilesystemItem item : list) {
                    if (filter == null || filter.matches(item)) {
                        items.add(item);
                        if (filter != null && filter.isFinished()) break;
                    }
                }
                if (filter != null) {
                    items = filter.reduce(items);
                   // items = filter.reduce(items);
                }
            }
        }
@@ -288,7 +286,7 @@
        return items;
    }
    public List<BorgFilesystemItem> getArchiveContent(File file) {
    public List<FilesystemItem> getArchiveContent(File file) {
        return archiveFilelistCache.load(file, null);
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/Filesystem.java
New file
@@ -0,0 +1,25 @@
package de.micromata.borgbutler.cache;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import org.apache.commons.collections4.CollectionUtils;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
public class Filesystem {
    public static FilesystemItem build(List<BorgFilesystemItem> itemList) {
        FilesystemItem root = new FilesystemItem();
        if (CollectionUtils.isEmpty(itemList)) {
            return root;
        }
        Collections.sort(itemList);
        for (BorgFilesystemItem item : itemList) {
            Path path = Paths.get(item.getPath());
            root.add(path, 0, item);
        }
        return root;
    }
}
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/FilesystemItem.java
New file
@@ -0,0 +1,187 @@
package de.micromata.borgbutler.cache;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
public class FilesystemItem extends BorgFilesystemItem implements Serializable {
    private transient static Logger log = LoggerFactory.getLogger(FilesystemItem.class);
    private static final long serialVersionUID = 6561019300264543523L;
    @Getter
    @Setter
    private List<FilesystemItem> childs;
    @Getter
    @Setter
    private String name;
    public FilesystemItem() {
    }
    public FilesystemItem(BorgFilesystemItem item) {
        this.path = item.getPath();
        this.mode = item.getMode();
        this.mtime = item.getMtime();
        this.flags = item.getFlags();
        this.size = item.getSize();
        this.type = item.getType();
        this.gid = item.getGid();
        this.group = item.getGroup();
        this.healthy = item.isHealthy();
        this.linktarget = item.getLinktarget();
        this.source = item.getSource();
        this.uid = item.getUid();
        this.user = item.getUser();
    }
    /**
     * If running in diff mode, this flag specifies the type of difference. Null represents unmodified.
     */
    public enum DiffStatus {NEW, REMOVED, MODIFIED}
    @Setter
    @Getter
    protected String displayPath;
    /**
     * If created by diff tool, this flag represents the type of difference.
     */
    @Getter
    @Setter
    private FilesystemItem.DiffStatus diffStatus;
    /**
     * If created by diff tool, this object holds the file item of the other archive (diff archive).
     */
    @Getter
    @Setter
    private FilesystemItem diffItem;
    /**
     * If created by diff tool, this String contains all differences between current and other item for {@link FilesystemItem.DiffStatus#MODIFIED}.
     * This String may used for displaying.
     */
    @Getter
    private String differences;
    /**
     * 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.getPath())) {
            log.error("*** Internal error: Differences should only be made on same path object: current='" + path
                    + "', other='" + diffItem.getPath() + "'.");
            return;
        }
        StringBuilder sb = new StringBuilder();
        appendDiff(sb, "type", this.type, diffItem.getType());
        //appendDiff(sb, "mode", this.mode, diffItem.mode); // Done by frontend (jsx)
        appendDiff(sb, "user", this.user, diffItem.getUser());
        appendDiff(sb, "group", this.group, diffItem.getGroup());
        appendDiff(sb, "uid", this.uid, diffItem.getUid());
        appendDiff(sb, "gid", this.gid, diffItem.getGid());
        //appendDiff(sb, "mtime", this.mtime, diffItem.mtime); // Done by frontend (jsx)
        //appendDiff(sb, "size", this.size, diffItem.size); // Done by frontend (jsx)
        if (sb.length() > 0) {
            diffStatus = FilesystemItem.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;
    }
    FilesystemItem add(Path path, int count, BorgFilesystemItem borgItem) {
        if (childs == null) {
            childs = new ArrayList<>();
        }
        if (count + 1 == path.getNameCount()) {
            FilesystemItem item = new FilesystemItem(borgItem);
            item.setName(path.getFileName().toString());
            childs.add(item);
            return item;
        }
        String name = path.getName(count).toString();
        FilesystemItem child = null;
        for (FilesystemItem ch : childs) {
            if (StringUtils.equals(ch.name, name)) {
                child = ch;
                break;
            }
        }
        if (child == null) {
            child = new FilesystemItem();
            child.setName(name);
            childs.add(child);
        }
        return child.add(path, count + 1, borgItem);
    }
    public FilesystemItem find(String pathString) {
        if (StringUtils.isBlank(pathString)) {
            return this;
        }
        Path path = Paths.get(pathString);
        if (path.getNameCount() == 0) {
            return this;
        }
        return find(path, 0);
    }
    private FilesystemItem find(Path path, int count) {
        if (childs == null) {
            return null;
        }
        for (FilesystemItem child : childs) {
            String name = path.getName(count).toString();
            if (StringUtils.equals(child.name, name)) {
                if (path.getNameCount() == count + 1)
                    return child;
                else
                    return child.find(path, count + 1);
            }
        }
        return null;
    }
    public int getFileNumber() {
        return path.hashCode();
    }
}
borgbutler-core/src/main/java/de/micromata/borgbutler/data/FileSystemFilter.java
@@ -1,15 +1,15 @@
package de.micromata.borgbutler.data;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import de.micromata.borgbutler.cache.FilesystemItem;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
public class FileSystemFilter {
    private Logger log = LoggerFactory.getLogger(FileSystemFilter.class);
@@ -22,8 +22,6 @@
    private Mode mode;
    @Getter
    private String currentDirectory;
    // For storing sub directories of the currentDirectory
    private Map<String, BorgFilesystemItem> subDirectories;
    @Getter
    @Setter
    private int maxResultSize = -1;
@@ -39,6 +37,43 @@
    @Getter
    private boolean finished;
    public List<FilesystemItem> buildMatchList(FilesystemItem root) {
        FilesystemItem current = root;
        if (StringUtils.isNotBlank(currentDirectory)) {
            current = root.find(currentDirectory);
            if (current == null) {
                current = root;
                log.warn("Directory '" + currentDirectory + "' not found. Searching in root directory.");
                currentDirectory = null;
            }
        }
        List<FilesystemItem> resultList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(current.getChilds())) {
            for (FilesystemItem item : current.getChilds()) {
                buildMatchList(item, resultList);
            }
        }
        return resultList;
    }
    private void buildMatchList(FilesystemItem item, List<FilesystemItem> resultList) {
        String pathString = item.getPath();
        if (mode == Mode.TREE && currentDirectory != null) {
            if (matchesRecursive(item)) {
                resultList.add(item);
            }
            return;
        }
        if (matches(item)) {
            resultList.add(item);
            if (CollectionUtils.isEmpty(item.getChilds()))
                return;
            for (FilesystemItem child : item.getChilds()) {
                buildMatchList(child, resultList);
            }
        }
    }
    /**
     * Please ensure that you call matches exactly ones for every file item. If matches returns true, the internal
     * item counter is incremented (for maxResultSize functionality).
@@ -48,7 +83,7 @@
     * @param item
     * @return true if the given item matches this filter.
     */
    public boolean matches(BorgFilesystemItem item) {
    public boolean matches(FilesystemItem item) {
        item.setDisplayPath(item.getPath());
        if (fileNumber != null) {
            if (item.getFileNumber() == fileNumber) {
@@ -57,24 +92,18 @@
            }
            return false;
        }
        if (mode == Mode.TREE) {
            // In this run only register all direct childs of currentDirectory.
            String topLevelDir = getTopLevel(item.getPath());
            if (topLevelDir == null) {
                // item is not inside the current directory.
        if (item.getPath() == null) {
                return false;
            }
            if (!subDirectories.containsKey(topLevelDir)) {
                subDirectories.put(topLevelDir, item);
        if (mode == Mode.TREE) {
            if (currentDirectory != null && item.getPath().startsWith(currentDirectory) == false) {
                return false;
            }
        }
        if (searchKeyWords == null && blackListSearchKeyWords == null) {
            processFinishedFlag();
            return true;
        }
        if (item.getPath() == null) {
            return false;
        }
        String path = item.getPath().toLowerCase();
        if (searchKeyWords != null) {
            for (String searchKeyWord : searchKeyWords) {
@@ -93,86 +122,27 @@
    }
    /**
     * After processing a list by using {@link #matches(BorgFilesystemItem)} you should call finally this method with
     * your result list to reduce the files and directories for mode {@link Mode#TREE}. For the mode {@link Mode#FLAT}
     * nothing is done.
     * Please ensure that you call matches exactly ones for every file item. If matches returns true, the internal
     * item counter is incremented (for maxResultSize functionality).
     * <br>
     * Reorders the list (.files will be displayed after normal files).
     * If the number of positive matches is greater than {@link #maxResultSize}, the finished flag is set to true.
     *
     * @param list
     * @return The original list for mode {@link Mode#FLAT} or the reduced list for the tree view.
     * @param item
     * @return true if the given item matches this filter.
     */
    public List<BorgFilesystemItem> reduce(List<BorgFilesystemItem> list) {
        if (mode == FileSystemFilter.Mode.TREE) {
            if (MapUtils.isEmpty(subDirectories)) {
                // If matches was not called before, do this now for getting all subdirectories.
                subDirectories = new HashMap<>();
                for (BorgFilesystemItem item : list) {
                    // Needed for building subdirectories...
                    this.matches(item);
    public boolean matchesRecursive(FilesystemItem item) {
        boolean matches = matches(item);
        if (matches == true) {
            return true;
                }
            }
            Set<String> set = new HashSet<>();
            List<BorgFilesystemItem> list2 = list;
            list = new ArrayList<>();
            for (BorgFilesystemItem item : list2) {
                String topLevel = getTopLevel(item.getPath());
                if (topLevel == null) {
                    continue;
                }
                if (set.contains(topLevel) == false) {
                    set.add(topLevel);
                    BorgFilesystemItem topItem = subDirectories.get(topLevel);
                    topItem.setDisplayPath(StringUtils.removeStart(topItem.getPath(), currentDirectory));
                    list.add(topItem);
                }
            }
            list2 = list;
            // Re-ordering (show dot files at last)
            list = new ArrayList<>();
            // First add normal files:
            for (BorgFilesystemItem item : list2) {
                if (!item.getDisplayPath().startsWith(".")) {
                    list.add(item);
                }
            }
            // Now add dot files:
            for (BorgFilesystemItem item : list2) {
                if (item.getDisplayPath().startsWith(".")) {
                    list.add(item);
        if (item.getChilds() != null) {
            for (FilesystemItem child : item.getChilds()) {
                if (matchesRecursive(child)) {
                    return true;
                }
            }
        }
        return list;
    }
    /**
     * @param path The path of the current item.
     * @return null if the item is not a child of the current directory otherwise the top level sub directory name of
     * the current directory.
     */
    String getTopLevel(String path) {
        if (StringUtils.isEmpty(currentDirectory)) {
            int pos = path.indexOf('/');
            if (pos < 0) {
                return path;
            }
            return path.substring(0, pos);
        }
        if (!path.startsWith(currentDirectory)) {
            // item is not a child of currentDirectory.
            return null;
        }
        if (path.length() <= currentDirectory.length() + 1) {
            // Don't show the current directory itself.
            return null;
        }
        path = StringUtils.removeStart(path, currentDirectory);
        int pos = path.indexOf('/');
        if (pos < 0) {
            return path;
        }
        return path.substring(0, pos);
        return false;
    }
    /**
@@ -227,9 +197,6 @@
     */
    public FileSystemFilter setMode(Mode mode) {
        this.mode = mode;
        if (mode == Mode.TREE) {
            this.subDirectories = new HashMap<>(); // needed only for tree view.
        }
        return this;
    }
borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgFilesystemItem.java
@@ -2,87 +2,49 @@
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)
     */
    @Getter
    @Setter
    private String type;
    protected String type;
    /**
     * Unix mode, e. g. <tt>drwxr-xr-x</tt>
     */
    @Getter
    @Setter
    private String mode;
    protected String mode;
    @Getter
    private String user;
    protected String user;
    @Getter
    private String group;
    protected String group;
    @Getter
    private long uid;
    protected long uid;
    @Getter
    private long gid;
    protected long gid;
    @Getter
    @Setter
    private String path;
    @Setter
    protected String path;
    @Getter
    private String displayPath;
    protected boolean healthy;
    @Getter
    private boolean healthy;
    protected String source;
    @Getter
    private String source;
    protected String linktarget;
    @Getter
    private String linktarget;
    @Getter
    private String flags;
    protected String flags;
    @Getter
    @Setter
    private String mtime;
    protected 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.
     */
    @Getter
    @Setter
    private int fileNumber;
    /**
     * If created by diff tool, this flag represents the type of difference.
     */
    @Getter
    @Setter
    private DiffStatus diffStatus;
    /**
     * If created by diff tool, this object holds the file item of the other archive (diff archive).
     */
    @Getter
    @Setter
    private BorgFilesystemItem diffItem;
    /**
     * If created by diff tool, this String contains all differences between current and other item for {@link DiffStatus#MODIFIED}.
     * This String may used for displaying.
     */
    @Getter
    private String differences;
    protected long size;
    @Override
    public int compareTo(BorgFilesystemItem o) {
@@ -97,100 +59,4 @@
        }
        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); // Done by frontend (jsx)
        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); // Done by frontend (jsx)
        //appendDiff(sb, "size", this.size, diffItem.size); // Done by frontend (jsx)
        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;
    }
}
borgbutler-core/src/test/java/de/micromata/borgbutler/DiffToolTest.java
@@ -1,6 +1,6 @@
package de.micromata.borgbutler;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import de.micromata.borgbutler.cache.FilesystemItem;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
@@ -12,8 +12,8 @@
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");
        FilesystemItem i1 = create("etc", true, "drwx------", 0, "2018-11-21");
        FilesystemItem 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));
@@ -23,19 +23,19 @@
    @Test
    void diffToolTest() {
        List<BorgFilesystemItem> l1 = null;
        List<BorgFilesystemItem> l2 = null;
        List<BorgFilesystemItem> result;
        List<FilesystemItem> l1 = null;
        List<FilesystemItem> l2 = null;
        List<FilesystemItem> 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());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.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());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
        l1 = create();
        l2 = create();
@@ -47,17 +47,17 @@
        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());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(2).getDiffStatus());
        assertEquals(FilesystemItem.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());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(2).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.MODIFIED, result.get(3).getDiffStatus());
        l1 = create();
        l2 = create();
@@ -66,14 +66,14 @@
        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());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.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());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(2).getDiffStatus());
        l1 = create();
@@ -82,12 +82,12 @@
        remove(l2, "home/kai/.borgbutler/borgbutler-config.json");
        result = DiffTool.extractDifferences(l1, l2);
        assertEquals(2, result.size());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus());
        result = DiffTool.extractDifferences(l2, l1);
        assertEquals(2, result.size());
        assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
        l1 = create();
        l2 = create();
@@ -96,18 +96,18 @@
        remove(l2, "home/kai/.borgbutler/borgbutler-config-bak.json");
        result = DiffTool.extractDifferences(l1, l2);
        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());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(2).getDiffStatus());
        result = DiffTool.extractDifferences(l2, l1);
        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());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus());
        assertEquals(FilesystemItem.DiffStatus.REMOVED, result.get(2).getDiffStatus());
    }
    private BorgFilesystemItem create(String path, boolean directory, String mode, long size, String mtime) {
        return new BorgFilesystemItem()
    private FilesystemItem create(String path, boolean directory, String mode, long size, String mtime) {
        return (FilesystemItem)new FilesystemItem()
                .setPath(path)
                .setType(directory ? "d" : "-")
                .setMode(mode)
@@ -115,8 +115,8 @@
                .setMtime(mtime);
    }
    private List<BorgFilesystemItem> create() {
        List<BorgFilesystemItem> list = new ArrayList<>();
    private List<FilesystemItem> create() {
        List<FilesystemItem> 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"));
@@ -128,13 +128,13 @@
        return list;
    }
    private void remove(List<BorgFilesystemItem> list, String path) {
        BorgFilesystemItem item = get(list, path);
    private void remove(List<FilesystemItem> list, String path) {
        FilesystemItem item = get(list, path);
        list.remove(item);
    }
    private BorgFilesystemItem get(List<BorgFilesystemItem> list, String path) {
        for (BorgFilesystemItem item : list) {
    private FilesystemItem get(List<FilesystemItem> list, String path) {
        for (FilesystemItem item : list) {
            if (item.getPath().equals(path)) {
                return item;
            }
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/ArchiveFilelistCacheTest.java
@@ -2,7 +2,6 @@
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,7 +23,7 @@
    @Test
    void readWriteTest() throws Exception {
        List<BorgFilesystemItem> list = createList(1000000);
        List<FilesystemItem> list = createList(1000000);
        ArchiveFilelistCache cache = new ArchiveFilelistCache(new File("out"), 100);
        cache.removeAllCacheFiles();
        BorgRepoConfig repoConfig = new BorgRepoConfig();
@@ -34,7 +33,7 @@
        cache.save(repoConfig, archive, list);
        log.info("Saving done.");
        log.info("Loading items from out dir.");
        List<BorgFilesystemItem> filesystemItems = cache.load(repoConfig, archive);
        List<FilesystemItem> filesystemItems = cache.load(repoConfig, archive);
        log.info("Loading " + filesystemItems.size() + " items done.");
        assertEquals(list.size(), filesystemItems.size());
        for (int i = 0; i < filesystemItems.size(); i++) {
@@ -45,7 +44,7 @@
    @Test
    void readWriteEmptyTest() throws Exception {
        List<BorgFilesystemItem> list = new ArrayList<>();
        List<FilesystemItem> list = new ArrayList<>();
        ArchiveFilelistCache cache = new ArchiveFilelistCache(new File("out"), 100);
        cache.removeAllCacheFiles();
        BorgRepoConfig repoConfig = new BorgRepoConfig();
@@ -53,14 +52,14 @@
        Archive archive = createArchive("2018-12-09");
        assertNull(cache.load(repoConfig, archive));
        cache.save(repoConfig, archive, list);
        List<BorgFilesystemItem> filesystemItems = cache.load(repoConfig, archive);
        List<FilesystemItem> filesystemItems = cache.load(repoConfig, archive);
        assertNull(cache.load(repoConfig, archive));
        cache.removeAllCacheFiles();
    }
    @Test
    void cleanUpMaximumSizeTest() throws Exception {
        List<BorgFilesystemItem> list = createList(1000000);
        List<FilesystemItem> list = createList(1000000);
        ArchiveFilelistCache cache = new ArchiveFilelistCache(new File("out"), 3);
        cache.removeAllCacheFiles();
        BorgRepoConfig repoConfig = new BorgRepoConfig();
@@ -96,7 +95,7 @@
    @Test
    void cleanUpExpiredTest() throws Exception {
        List<BorgFilesystemItem> list = createList(1000);
        List<FilesystemItem> list = createList(1000);
        ArchiveFilelistCache cache = new ArchiveFilelistCache(new File("out"), 3);
        cache.removeAllCacheFiles();
        BorgRepoConfig repoConfig = new BorgRepoConfig();
@@ -123,16 +122,16 @@
        cache.removeAllCacheFiles();
    }
    private List<BorgFilesystemItem> createList(int number) throws Exception {
        List<BorgFilesystemItem> list = new ArrayList<>();
    private List<FilesystemItem> createList(int number) throws Exception {
        List<FilesystemItem> list = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            list.add(create(i));
        }
        return list;
    }
    private BorgFilesystemItem create(int i) throws Exception {
        BorgFilesystemItem item = new BorgFilesystemItem();
    private FilesystemItem create(int i) throws Exception {
        FilesystemItem item = new FilesystemItem();
        set(item, "type", "-").set(item, "mode", "drwxr-xr-x")
                .set(item, "user", "kai").set(item, "group", "user")
                .set(item, "path", "/Users/kai/Test" + i + ".java").set(item, "size", 1000);
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java
@@ -5,7 +5,6 @@
import de.micromata.borgbutler.config.ConfigurationHandler;
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.data.Repository;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import org.apache.commons.collections4.CollectionUtils;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -57,7 +56,7 @@
                assertNotNull(archive2);
                archive = ButlerCache.getInstance().getArchive(repoConfig.getRepo(), archive.getId());
                assertNotNull(archive2);
                List<BorgFilesystemItem> content = ButlerCache.getInstance().getArchiveContent(repoConfig, archive2);
                List<FilesystemItem> content = ButlerCache.getInstance().getArchiveContent(repoConfig, archive2);
                log.info("Number of items (content) of archive: " + content.size());
                content = ButlerCache.getInstance().getArchiveContent(repoConfig, archive2);
                log.info("Number of items (content) of archive: " + content.size());
borgbutler-core/src/test/java/de/micromata/borgbutler/cache/FilesystemTest.java
New file
@@ -0,0 +1,34 @@
package de.micromata.borgbutler.cache;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class FilesystemTest {
    private static Logger log = LoggerFactory.getLogger(FilesystemTest.class);
    @Test
    void filesystemTest() {
        List<BorgFilesystemItem> borgList = new ArrayList<>();
        borgList.add(create("home/kai/test.xls"));
        FilesystemItem root = Filesystem.build(borgList);
        List<FilesystemItem> list  = root.getChilds();
        assertEquals(1, list.size());
        assertEquals("home", list.get(0).getName());
        assertEquals("kai", list.get(0).getChilds().get(0).getName());
        assertEquals("test.xls", list.get(0).getChilds().get(0).getChilds().get(0).getName());
        assertEquals("home", root.find("home").getName());
        assertEquals("kai", root.find("home/kai").getName());
    }
    private BorgFilesystemItem create(String path) {
        return new BorgFilesystemItem().setPath(path);
    }
}
borgbutler-core/src/test/java/de/micromata/borgbutler/data/FileSystemFilterTest.java
@@ -1,6 +1,6 @@
package de.micromata.borgbutler.data;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import de.micromata.borgbutler.cache.FilesystemItem;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
@@ -21,31 +21,32 @@
        assertEquals("kai", filter.getTopLevel("home/kai"));
        assertEquals("kai", filter.getTopLevel("home/kai/test.java"));
        assertNull(filter.getTopLevel("etc/test"));
        List<BorgFilesystemItem> list = createList();
        List<FilesystemItem> list = createList();
        filter.setCurrentDirectory("").setMode(FileSystemFilter.Mode.TREE);
        for (BorgFilesystemItem item : list) {
        for (FilesystemItem item : list) {
            if (filter.matches(item)) {
                // Do nothing.
            }
        }
        list = filter.reduce(list);
       // list = filter.reduce(list);
        assertEquals(2, list.size());
        list = createList();
        filter.setCurrentDirectory("home");
        for (BorgFilesystemItem item : list) {
        for (FilesystemItem item : list) {
            if (filter.matches(item)) {
                // Do nothing.
            }
        }
        list = filter.reduce(list);
      //  list = filter.reduce(list);
        assertEquals(4, list.size());
        assertEquals(".bashrc", list.get(0).getDisplayPath());
        assertEquals(".borgbutler", list.get(1).getDisplayPath());
    }
    private BorgFilesystemItem create(String path, boolean directory) {
        BorgFilesystemItem item = new BorgFilesystemItem().setPath(path);
    private FilesystemItem create(String path, boolean directory) {
        FilesystemItem item = new FilesystemItem();
        item.setPath(path);
        if (directory) {
            item.setType("d");
        } else {
@@ -54,8 +55,8 @@
        return item;
    }
    private List<BorgFilesystemItem> createList() {
        List<BorgFilesystemItem> list = new ArrayList<>();
    private List<FilesystemItem> createList() {
        List<FilesystemItem> list = new ArrayList<>();
        list.add(create("home", true));
        list.add(create("home/.bashrc", false));
        list.add(create("home/.borgbutler", true));
borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
@@ -2,7 +2,7 @@
import de.micromata.borgbutler.cache.ButlerCache;
import de.micromata.borgbutler.config.ConfigurationHandler;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import de.micromata.borgbutler.cache.FilesystemItem;
import de.micromata.borgbutler.server.jetty.JettyServer;
import de.micromata.borgbutler.server.user.SingleUserManager;
import de.micromata.borgbutler.server.user.UserManager;
@@ -139,7 +139,7 @@
    private static void printArchiveContent(String fileName) {
        File file = new File(fileName);
        List<BorgFilesystemItem> fileList = ButlerCache.getInstance().getArchiveContent(file);
        List<FilesystemItem> fileList = ButlerCache.getInstance().getArchiveContent(file);
        boolean parseFormatExceptionPrinted = false;
        if (fileList != null && fileList.size() > 0) {
            TimeZone tz = TimeZone.getTimeZone("UTC");
@@ -149,7 +149,7 @@
            File out = new File(FilenameUtils.getBaseName(fileName) + ".txt.gz");
            log.info("Writing file list to: " + out.getAbsolutePath());
            try (PrintWriter writer = new PrintWriter(new BufferedOutputStream(new GzipCompressorOutputStream(new FileOutputStream(out))))) {
                for (BorgFilesystemItem item : fileList) {
                for (FilesystemItem item : fileList) {
                    String time = item.getMtime();
                    if (time.indexOf('T') > 0) {
                        try {
borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
@@ -9,7 +9,7 @@
import de.micromata.borgbutler.data.FileSystemFilter;
import de.micromata.borgbutler.data.Repository;
import de.micromata.borgbutler.json.JsonUtils;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import de.micromata.borgbutler.cache.FilesystemItem;
import de.micromata.borgbutler.utils.DirUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@@ -79,7 +79,7 @@
                .setMaxResultSize(maxSize)
                .setMode(mode)
                .setCurrentDirectory(currentDirectory);
        List<BorgFilesystemItem> items = null;
        List<FilesystemItem> items = null;
        if (StringUtils.isBlank(diffArchiveId)) {
            // Get file list (without running diff).
            items = ButlerCache.getInstance().getArchiveContent(archiveId, force,
@@ -90,7 +90,7 @@
        } else {
            filter.setMode(FileSystemFilter.Mode.FLAT).setMaxResultSize(-1);
            items = ButlerCache.getInstance().getArchiveContent(archiveId, true, filter);
            List<BorgFilesystemItem> diffItems = ButlerCache.getInstance().getArchiveContent(diffArchiveId, true,
            List<FilesystemItem> diffItems = ButlerCache.getInstance().getArchiveContent(diffArchiveId, true,
                    filter);
            items = DiffTool.extractDifferences(items, diffItems);
            filter.setMaxResultSize(maxSize)
@@ -110,7 +110,7 @@
    public Response restore(@QueryParam("archiveId") String archiveId, @QueryParam("fileNumber") int fileNumber) {
        log.info("Requesting file #" + fileNumber + " of archive '" + archiveId + "'.");
        FileSystemFilter filter = new FileSystemFilter().setFileNumber(fileNumber);
        List<BorgFilesystemItem> items = ButlerCache.getInstance().getArchiveContent(archiveId, false,
        List<FilesystemItem> items = ButlerCache.getInstance().getArchiveContent(archiveId, false,
                filter);
        if (CollectionUtils.isEmpty(items)) {
            log.error("Requested file #" + fileNumber + " not found in archive '" + archiveId
@@ -131,7 +131,7 @@
        }
        BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(archive.getRepoId());
        try {
            BorgFilesystemItem item = items.get(0);
            FilesystemItem item = items.get(0);
            File restoreHomeDir = ConfigurationHandler.getConfiguration().getRestoreHomeDir();
            File restoreDir = BorgCommands.extractFiles(restoreHomeDir, repoConfig, archive, item.getPath());
            List<java.nio.file.Path> files = DirUtils.listFiles(restoreDir.toPath());