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

Kai Reinhard
14.23.2019 8290a1feaf4bec966be00919cbe5963d4b6e3867
Merge pull request #18 from kreinhard/master

Some work...
9 files added
2 files renamed
20 files modified
741 ■■■■ changed files
README.adoc 4 ●●●● patch | view | raw | blame | history
borgbutler-core/README.adoc 16 ●●●●● patch | view | raw | blame | history
borgbutler-core/demo/createFiles.sh 70 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommand.java 2 ●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java 6 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java 12 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java 3 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java 23 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java 4 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/data/DiffFileSystemFilter.java 19 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/data/FileSystemFilter.java 169 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java 149 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java 4 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobResult.java 2 ●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgFilesystemItem.java 3 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/resources/demodata/archive-info-borgbutlerdemo-2019-01-12_01-00.json.gz patch | view | raw | blame | history
borgbutler-core/src/main/resources/demodata/archive-info-borgbutlerdemo-2019-01-13_01-00.json.gz patch | view | raw | blame | history
borgbutler-core/src/main/resources/demodata/archive-list-borgbutlerdemo-2019-01-12_01-00.json.gz patch | view | raw | blame | history
borgbutler-core/src/main/resources/demodata/archive-list-borgbutlerdemo-2019-01-13_01-00.json.gz patch | view | raw | blame | history
borgbutler-core/src/main/resources/demodata/repo-info.json.gz patch | view | raw | blame | history
borgbutler-core/src/main/resources/demodata/repo-list.json.gz patch | view | raw | blame | history
borgbutler-core/src/test/java/de/micromata/borgbutler/DiffFileSystemFilterTest.java 28 ●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java 9 ●●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java 31 ●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/general/IconComponents.jsx 14 ●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/archives/ArchiveView.jsx 2 ●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/archives/FileListEntry.jsx 56 ●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/archives/FileListPanel.jsx 6 ●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/archives/FileListTable.jsx 5 ●●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/config/ConfigurationServerTab.jsx 10 ●●●●● patch | view | raw | blame | history
borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx 94 ●●●●● patch | view | raw | blame | history
README.adoc
@@ -77,6 +77,10 @@
3. `npm start` (opens the web browser on port 3000)
4. Start `de.micromata.borgbutler.server.Main` (ignore the opened browser window for port 9042)
=== Profiling heap, cpu and everything using JProfiler
JProfiler is an excellent tool for analysing your software. BorgButler was optimized regarding heap memory and CPU usage by
using https://www.ej-technologies.com/products/jprofiler/overview.html[JProfiler from EJ Technologies^]
== Ideas
=== 2 factor authentication
https://github.com/j256/two-factor-auth
borgbutler-core/README.adoc
New file
@@ -0,0 +1,16 @@
Micromata BorgBackup-Butler
===========================
Micromata GmbH, Kai Reinhard
:toc:
:toclevels: 4
Copyright (C) 2019
ifdef::env-github,env-browser[:outfilesuffix: .adoc]
== Development
=== Creating test data
1. Install virtual debian system
2. `apt install net-tools curl`
3. Execute script `./createFiles.sh` on debian host (borg is installed automatically)
4. See the result files in `out.tar`.
borgbutler-core/demo/createFiles.sh
New file
@@ -0,0 +1,70 @@
#!/bin/bash
export BORG_PASSPHRASE='borgbutler123'
export BORG_COMMAND='/root/bin/borg-linux64'
export TEST_DIR='/root/borgbutler-demo'
if [ -f $BORG_COMMAND ]; then
  echo Borg command already exists...
else
  echo Downloading borg;
  mkdir /root/bin
  cd /root/bin
  curl -LJO https://github.com/borgbackup/borg/releases/download/1.1.8/borg-linux64
  chmod 700 $BORG_COMMAND
fi;
echo Creating backup dir /backup-test...
rm -rf /backup-test
mkdir /backup-test
echo Initializing borg backup...
$BORG_COMMAND init --encryption=repokey /backup-test
function backup() {
echo Creating backup...
$BORG_COMMAND create --filter AME                    \
                     --stats                         \
                     --progress                      \
                     --show-rc                       \
                     --compression lz4               \
                     --exclude-caches                \
                     /backup-test::borgbutlerdemo-$1 \
                     /home /root /etc /usr/bin /usr/sbin /opt
}
rm -rf $TEST_DIR
mkdir $TEST_DIR
cd $TEST_DIR
touch README.txt
chmod 700 README.txt
echo `ls /usr` > filelist
touch oldfile
backup 2019-01-12_01-00
rm oldfile
mkdir newDir
touch newDir/newfile
chown borgbutler.users README.txt
chmod 755 README.txt
echo `ls /` >> filelist
backup 2019-01-13_01-00
cd /root
rm -rf out
mkdir out
cd out
$BORG_COMMAND info --json /backup-test >repo-info.json
$BORG_COMMAND list --json /backup-test >repo-list.json
$BORG_COMMAND info --json /backup-test::borgbutlerdemo-2019-01-12_01-00 >archive-info-borgbuterldemo-2019-01-12_01-00.json
$BORG_COMMAND info --json /backup-test::borgbutlerdemo-2019-01-13_01-00 >archive-info-borgbuterldemo-2019-01-13_01-00.json
$BORG_COMMAND list --json-lines /backup-test::borgbutlerdemo-2019-01-12_01-00 >archive-list-borgbuterldemo-2019-01-12_01-00.json
$BORG_COMMAND list --json-lines /backup-test::borgbutlerdemo-2019-01-13_01-00 >archive-list-borgbuterldemo-2019-01-13_01-00.json
gzip -9 *
cd /root
tar cvf out.tar out
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommand.java
@@ -55,7 +55,7 @@
        return this;
    }
    String getRepoArchive() {
    public String getRepoArchive() {
        if (archive == null) {
            if (repoConfig == null) {
                return null;
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
@@ -3,6 +3,7 @@
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.data.Repository;
import de.micromata.borgbutler.demo.DemoRepos;
import de.micromata.borgbutler.jobs.JobResult;
import de.micromata.borgbutler.json.JsonUtils;
import de.micromata.borgbutler.json.borg.*;
@@ -72,6 +73,7 @@
                .setEncryption(repoInfo.getEncryption())
                .setSecurityDir(repoInfo.getSecurityDir())
                .setLastCacheRefresh(DateUtils.format(LocalDateTime.now()));
        DemoRepos.repoWasRead(repoConfig, repository);
        return repository;
    }
@@ -178,7 +180,7 @@
                .setTotal(archive.getStats().getNfiles());
        BorgJob<List<BorgFilesystemItem>> job = BorgQueueExecutor.getInstance().execute(new BorgJob<List<BorgFilesystemItem>>(command) {
            @Override
            protected void processStdOutLine(String line, int level) {
            public void processStdOutLine(String line, int level) {
                BorgFilesystemItem item = JsonUtils.fromJson(BorgFilesystemItem.class, line);
                item.setMtime(DateUtils.format(item.getMtime()));
                payload.add(item);
@@ -190,7 +192,7 @@
        });
        job.payload = new ArrayList<>();
        JobResult<String> jobResult = job.getResult();
        if (jobResult == null ||jobResult.getStatus() != JobResult.Status.OK) {
        if (jobResult == null || jobResult.getStatus() != JobResult.Status.OK) {
            return null;
        }
        List<BorgFilesystemItem> items = job.payload;
borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java
@@ -3,7 +3,9 @@
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.config.ConfigurationHandler;
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.demo.DemoRepos;
import de.micromata.borgbutler.jobs.AbstractCommandLineJob;
import de.micromata.borgbutler.jobs.JobResult;
import de.micromata.borgbutler.json.JsonUtils;
import de.micromata.borgbutler.json.borg.ProgressInfo;
import lombok.AccessLevel;
@@ -70,7 +72,7 @@
        return commandLine;
    }
    protected void processStdErrLine(String line, int level) {
    public void processStdErrLine(String line, int level) {
        try {
            if (StringUtils.startsWith(line, "{\"message")) {
                ProgressInfo message = JsonUtils.fromJson(ProgressInfo.class, line);
@@ -103,6 +105,14 @@
    }
    @Override
    public JobResult<String> execute() {
        if (DemoRepos.isDemo(command.getRepoConfig().getRepo())) {
            return DemoRepos.execute(this);
        }
        return super.execute();
    }
    @Override
    public BorgJob<?> clone() {
        BorgJob<?> clone = new BorgJob<>();
        if (command != null) {
borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -278,12 +278,9 @@
                if (CollectionUtils.isNotEmpty(list)) {
                    archiveFilelistCache.save(repoConfig, archive, list);
                    items = new ArrayList<>();
                    int fileNumber = -1;
                    Iterator<BorgFilesystemItem> it = list.iterator(); // Don't use for-each (ConcurrentModificationException)
                    while (it.hasNext()) {
                        BorgFilesystemItem item = it.next();
                        ++fileNumber;
                        item.setFileNumber(fileNumber);
                        if (filter == null || filter.matches(item)) {
                            items.add(item);
                            if (filter != null && filter.isFinished()) break;
borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
@@ -2,6 +2,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.micromata.borgbutler.demo.DemoRepos;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
@@ -32,6 +33,10 @@
    @Getter
    private int maxArchiveContentCacheCapacityMb = 100;
    @Getter
    @Setter
    private boolean showDemoRepos = true;
    /**
     * Default is restore inside BorgButler's home dir (~/.borgbutler/restore).
     */
@@ -41,7 +46,6 @@
    @JsonIgnore
    private File restoreHomeDir;
    @Getter
    private List<BorgRepoConfig> repoConfigs = new ArrayList<>();
    public void add(BorgRepoConfig repoConfig) {
@@ -52,7 +56,7 @@
        if (idOrName == null) {
            return null;
        }
        for (BorgRepoConfig repoConfig : repoConfigs) {
        for (BorgRepoConfig repoConfig : getRepoConfigs()) {
            if (StringUtils.equals(idOrName, repoConfig.getRepo()) || StringUtils.equals(idOrName, repoConfig.getId())) {
                return repoConfig;
            }
@@ -77,5 +81,20 @@
    public void copyFrom(Configuration other) {
        this.borgCommand = other.borgCommand;
        this.maxArchiveContentCacheCapacityMb = other.maxArchiveContentCacheCapacityMb;
        this.showDemoRepos = other.showDemoRepos;
    }
    public List<BorgRepoConfig> getRepoConfigs() {
        if (!ConfigurationHandler.getConfiguration().isShowDemoRepos()) {
            return repoConfigs;
        }
        List<BorgRepoConfig> result = new ArrayList<>();
        result.addAll(repoConfigs);
        DemoRepos.addDemoRepos(result);
        return result;
    }
    List<BorgRepoConfig> _getRepoConfigs() {
        return repoConfigs;
    }
}
borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java
@@ -48,8 +48,8 @@
                }
            }
            this.configuration = JsonUtils.fromJson(configClazz, json);
            if (this.configuration.getRepoConfigs() != null) {
                for (BorgRepoConfig repoConfig : this.configuration.getRepoConfigs()) {
            if (this.configuration._getRepoConfigs() != null) {
                for (BorgRepoConfig repoConfig : this.configuration._getRepoConfigs()) {
                    if (StringUtils.isBlank(repoConfig.getDisplayName())) {
                        repoConfig.setDisplayName(repoConfig.getRepo());
                    }
borgbutler-core/src/main/java/de/micromata/borgbutler/data/DiffFileSystemFilter.java
File was renamed from borgbutler-core/src/main/java/de/micromata/borgbutler/DiffTool.java
@@ -1,4 +1,4 @@
package de.micromata.borgbutler;
package de.micromata.borgbutler.data;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import org.slf4j.Logger;
@@ -11,15 +11,15 @@
/**
 * Extracts the differences between two archives of one repo.
 */
public class DiffTool {
    private static Logger log = LoggerFactory.getLogger(DiffTool.class);
public class DiffFileSystemFilter extends FileSystemFilter {
    private Logger log = LoggerFactory.getLogger(DiffFileSystemFilter.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) {
    public 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<>();
@@ -37,7 +37,8 @@
            }
            int cmp = current.compareTo(other);
            if (cmp == 0) { // Items represents both the same file system item.
                if (current.equals(other)) {
                if (!checkDirectoryMatchAndRegisterSubDirectories(current) ||
                        current.equals(other)) {
                    current = other = null; // increment both iterators.
                    continue;
                }
@@ -48,10 +49,14 @@
                result.add(current);
                current = other = null; // increment both iterators.
            } else if (cmp < 0) {
                result.add(current.setDiffStatus(BorgFilesystemItem.DiffStatus.NEW));
                if (checkDirectoryMatchAndRegisterSubDirectories(current)) {
                    result.add(current.setDiffStatus(BorgFilesystemItem.DiffStatus.NEW));
                }
                current = currentIt.hasNext() ? currentIt.next() : null;
            } else {
                result.add(other.setDiffStatus(BorgFilesystemItem.DiffStatus.REMOVED));
                if (checkDirectoryMatchAndRegisterSubDirectories(other)) {
                    result.add(other.setDiffStatus(BorgFilesystemItem.DiffStatus.REMOVED));
                }
                other = otherIt.hasNext() ? otherIt.next() : null;
            }
        }
borgbutler-core/src/main/java/de/micromata/borgbutler/data/FileSystemFilter.java
@@ -3,8 +3,6 @@
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
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;
@@ -57,16 +55,8 @@
            }
            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.
                return false;
            }
            if (!subDirectories.containsKey(topLevelDir)) {
                subDirectories.put(topLevelDir, item);
            }
        if (!checkDirectoryMatchAndRegisterSubDirectories(item)) {
            return false;
        }
        if (searchKeyWords == null && blackListSearchKeyWords == null) {
            processFinishedFlag();
@@ -103,79 +93,47 @@
     * @return The original list for mode {@link Mode#FLAT} or the reduced list for the tree view.
     */
    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);
                }
        if (mode != FileSystemFilter.Mode.TREE) {
            return list;
        }
        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;
            }
            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);
            if (set.contains(topLevel) == false) {
                set.add(topLevel);
                BorgFilesystemItem topItem = subDirectories.get(topLevel);
                if (topItem == null) {
                    log.error("Internal error, can't find subDirectory: " + topLevel);
                } else {
                    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);
                }
        }
        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);
                }
        }
        // Now add dot files:
        for (BorgFilesystemItem item : list2) {
            if (item.getDisplayPath().startsWith(".")) {
                list.add(item);
            }
        }
        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);
    }
    /**
     * @param searchString The search string. If this string contains several key words separated by white chars,
     *                     all key words must be found.
     * @return this for chaining.
@@ -211,6 +169,71 @@
    }
    /**
     * This method has only effect in FLAT view. This method has to be called for every item of the list before
     * {@link #reduce(List)} may work, because this method registers sub directories of the current directory needed
     * by {@link #reduce(List)}.
     *
     * @param item
     * @return false, if the given item is not a sub item of the current directory. You may skip further checkings for this
     * item. If true, this item might be part of the result.
     */
    protected boolean checkDirectoryMatchAndRegisterSubDirectories(BorgFilesystemItem item) {
        if (mode != Mode.TREE) {
            return true;
        }
        if (StringUtils.isNotEmpty(currentDirectory) && !item.getPath().startsWith(currentDirectory)) {
            // item is not inside the current directory.
            return false;
        }
        // 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.
            return false;
        }
        if (!subDirectories.containsKey(topLevelDir)) {
            subDirectories.put(topLevelDir, item);
        }
        return true;
    }
    /**
     * currentDirectory '': <tt>home</tt> -&gt; <tt>home</tt><br>
     * currentDirectory '': <tt>home/kai</tt> -&gt; <tt>home</tt><br>
     * currentDirectory 'home': <tt>home</tt> -&gt; <tt>null</tt><br>
     * currentDirectory 'home': <tt>home/kai</tt> -&gt; <tt>kai</tt><br>
     * currentDirectory 'home': <tt>home/kai/test.java</tt> -&gt; <tt>kai</tt><br>
     *
     * @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);
    }
    /**
     * @param mode
     * @return this for chaining.
     */
borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java
New file
@@ -0,0 +1,149 @@
package de.micromata.borgbutler.demo;
import de.micromata.borgbutler.BorgCommand;
import de.micromata.borgbutler.BorgJob;
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.config.ConfigurationHandler;
import de.micromata.borgbutler.config.Definitions;
import de.micromata.borgbutler.data.Repository;
import de.micromata.borgbutler.jobs.JobResult;
import de.micromata.borgbutler.json.JsonUtils;
import de.micromata.borgbutler.json.borg.ProgressInfo;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class DemoRepos {
    private enum Type {FAST, SLOW, VERY_SLOW}
    private static Logger log = LoggerFactory.getLogger(DemoRepos.class);
    private static final String DEMO_IDENTIFIER = "borgbutler-demo";
    private static final String[] REPOS = {"fast", "slow", "very-slow"};
    private static List<BorgRepoConfig> demoRepos;
    /**
     * If configured by the user, demo repositories are added to the given list. If not configured this method does nothing.
     *
     * @param repositoryList
     */
    public static void addDemoRepos(List<BorgRepoConfig> repositoryList) {
        if (!ConfigurationHandler.getConfiguration().isShowDemoRepos()) {
            return;
        }
        init();
        for (BorgRepoConfig repo : demoRepos) {
            repositoryList.add(repo);
        }
    }
    public static boolean isDemo(String name) {
        return StringUtils.startsWith(name, DEMO_IDENTIFIER);
    }
    public static void repoWasRead(BorgRepoConfig repoConfig, Repository repository) {
        if (!isDemo(repository.getName())) {
            return;
        }
        repository.setId(repository.getId() + "-" + REPOS[getType(repoConfig).ordinal()]);
    }
    public static JobResult<String> execute(BorgJob job) {
        BorgCommand command = job.getCommand();
        if (!StringUtils.equalsAny(command.getCommand(), "list", "info")) {
            log.info("Commmand '" + command.getCommand() + "' not supported for demo repositories.");
            return new JobResult<String>().setStatus(JobResult.Status.ERROR);
        }
        StringBuilder sb = new StringBuilder();
        boolean archive = command.getArchive() != null;
        if (archive) {
            sb.append("archive-");
        } else {
            sb.append("repo-");
        }
        sb.append(command.getCommand());
        if (archive) {
            sb.append("-").append(command.getArchive());
        }
        sb.append(".json.gz");
        int wait = 0;
        Type type = getType(command.getRepoConfig());
        if (type == Type.VERY_SLOW) {
            wait = 10;
        } else if (type == Type.SLOW) {
            wait = 1;
        }
        String file = sb.toString();
        log.info("Loading demo archive from '" + file + "'...");
        try (InputStream inputStream = new GzipCompressorInputStream(DemoRepos.class.getResourceAsStream("/demodata/" + file))) {
            if (wait > 0) {
                ProgressInfo progress = new ProgressInfo()
                        .setMessage("Faked demo progress")
                        .setTotal(10 * wait);
                for (int i = 0; i < 10 * wait; i++) {
                    if (job.isCancellationRequested()) {
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ex) {
                        // Do nothing.
                    }
                    job.processStdErrLine(JsonUtils.toJson(progress.setCurrent(i)), 0);
                }
            }
            if (archive && "list".equals(command.getCommand())) {
                try (Scanner scanner = new Scanner(inputStream)) {
                    while (scanner.hasNextLine()) {
                        String line = scanner.nextLine();
                        job.processStdOutLine(line, 0);
                    }
                    return new JobResult<String>().setStatus(JobResult.Status.OK);
                }
            } else {
                StringWriter writer = new StringWriter();
                IOUtils.copy(inputStream, writer, Definitions.STD_CHARSET);
                return new JobResult<String>().setResultObject(writer.toString()).setStatus(JobResult.Status.OK);
            }
        } catch (IOException ex) {
            log.error("Error while reading demo file '" + file + "': " + ex.getMessage() + ".");
            return null;
        }
    }
    private static Type getType(BorgRepoConfig repoConfig) {
        if (repoConfig.getRepo().endsWith("very-slow")) {
            return Type.VERY_SLOW;
        } else if (repoConfig.getRepo().endsWith("slow")) {
            return Type.SLOW;
        }
        return Type.FAST;
    }
    private static void init() {
        synchronized (DEMO_IDENTIFIER) {
            if (demoRepos != null) {
                return;
            }
            demoRepos = new ArrayList<>();
            demoRepos.add(new BorgRepoConfig()
                    .setRepo(DEMO_IDENTIFIER + "-fast")
                    .setDisplayName("Demo repository fast"));
            demoRepos.add(new BorgRepoConfig()
                    .setRepo(DEMO_IDENTIFIER + "-slow")
                    .setDisplayName("Demo repository slow"));
            demoRepos.add(new BorgRepoConfig()
                    .setRepo(DEMO_IDENTIFIER + "-very-slow")
                    .setDisplayName("Demo repository very-slow"));
        }
    }
}
borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java
@@ -101,7 +101,7 @@
        return result;
    }
    protected void processStdOutLine(String line, int level) {
    public void processStdOutLine(String line, int level) {
        //log.info(line);
        try {
            outputStream.write(line.getBytes());
@@ -111,7 +111,7 @@
        }
    }
    protected void processStdErrLine(String line, int level) {
    public void processStdErrLine(String line, int level) {
        //log.info(line);
        try {
            errorOutputStream.write(line.getBytes());
borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/JobResult.java
@@ -7,7 +7,7 @@
public class JobResult<T> {
    public enum Status {OK, ERROR}
    @Getter
    @Setter(AccessLevel.PACKAGE)
    @Setter
    private Status status;
    @Getter
    @Setter
borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgFilesystemItem.java
@@ -66,7 +66,8 @@
     */
    @Getter
    @Setter
    private int fileNumber;
    private int fileNumber = -1;
    /**
     * If created by diff tool, this flag represents the type of difference.
     */
borgbutler-core/src/main/resources/demodata/archive-info-borgbutlerdemo-2019-01-12_01-00.json.gz
Binary files differ
borgbutler-core/src/main/resources/demodata/archive-info-borgbutlerdemo-2019-01-13_01-00.json.gz
Binary files differ
borgbutler-core/src/main/resources/demodata/archive-list-borgbutlerdemo-2019-01-12_01-00.json.gz
Binary files differ
borgbutler-core/src/main/resources/demodata/archive-list-borgbutlerdemo-2019-01-13_01-00.json.gz
Binary files differ
borgbutler-core/src/main/resources/demodata/repo-info.json.gz
Binary files differ
borgbutler-core/src/main/resources/demodata/repo-list.json.gz
Binary files differ
borgbutler-core/src/test/java/de/micromata/borgbutler/DiffFileSystemFilterTest.java
File was renamed from borgbutler-core/src/test/java/de/micromata/borgbutler/DiffToolTest.java
@@ -1,5 +1,6 @@
package de.micromata.borgbutler;
import de.micromata.borgbutler.data.DiffFileSystemFilter;
import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
import org.junit.jupiter.api.Test;
@@ -9,7 +10,7 @@
import static org.junit.jupiter.api.Assertions.*;
public class DiffToolTest {
public class DiffFileSystemFilterTest {
    @Test
    void differencesTest() {
        BorgFilesystemItem i1 = create("etc", true, "drwx------", 0, "2018-11-21");
@@ -28,33 +29,34 @@
        List<BorgFilesystemItem> l1 = null;
        List<BorgFilesystemItem> l2 = null;
        List<BorgFilesystemItem> result;
        assertEquals(0, DiffTool.extractDifferences(l1, l2).size());
        DiffFileSystemFilter filter = new DiffFileSystemFilter();
        assertEquals(0, filter.extractDifferences(l1, l2).size());
        l1 = create();
        result = DiffTool.extractDifferences(l1, l2);
        result = filter.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);
        result = filter.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);
        result = filter.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);
        result = filter.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);
        result = filter.extractDifferences(l2, l1);
        assertEquals(4, result.size());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
@@ -66,12 +68,12 @@
        remove(l2, "etc"); // 0
        remove(l2, "etc/passwd"); // 1
        remove(l1, "home/kai/.borgbutler/borgbutler-config.json"); // 2
        result = DiffTool.extractDifferences(l1, l2);
        result = filter.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);
        result = filter.extractDifferences(l2, l1);
        assertEquals(3, result.size());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
@@ -82,11 +84,11 @@
        l2 = create();
        remove(l1, "home/kai/.borgbutler/borgbutler-config-bak.json");
        remove(l2, "home/kai/.borgbutler/borgbutler-config.json");
        result = DiffTool.extractDifferences(l1, l2);
        result = filter.extractDifferences(l1, l2);
        assertEquals(2, result.size());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(0).getDiffStatus());
        assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus());
        result = DiffTool.extractDifferences(l2, l1);
        result = filter.extractDifferences(l2, l1);
        assertEquals(2, result.size());
        assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(BorgFilesystemItem.DiffStatus.REMOVED, result.get(1).getDiffStatus());
@@ -96,12 +98,12 @@
        remove(l1, "home/kai");
        remove(l1, "home/kai/.borgbutler");
        remove(l2, "home/kai/.borgbutler/borgbutler-config-bak.json");
        result = DiffTool.extractDifferences(l1, l2);
        result = filter.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());
        result = DiffTool.extractDifferences(l2, l1);
        result = filter.extractDifferences(l2, l1);
        assertEquals(3, result.size());
        assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(0).getDiffStatus());
        assertEquals(BorgFilesystemItem.DiffStatus.NEW, result.get(1).getDiffStatus());
borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java
@@ -1,12 +1,9 @@
package de.micromata.borgbutler.server;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import de.micromata.borgbutler.cache.ButlerCache;
import de.micromata.borgbutler.config.Configuration;
import de.micromata.borgbutler.config.ConfigurationHandler;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,16 +12,11 @@
    private static Logger log = LoggerFactory.getLogger(ServerConfiguration.class);
    private final static String[] SUPPORTED_LANGUAGES = {"en", "de"};
    public static final int WEBSERVER_PORT_DEFAULT = 9042;
    private static final boolean SHOW_TEST_DATA_PREF_DEFAULT = false;
    private static final boolean WEB_DEVELOPMENT_MODE_PREF_DEFAULT = false;
    private static String applicationHome;
    private int port = WEBSERVER_PORT_DEFAULT;
    @Getter
    @Setter
    @JsonIgnore
    private boolean showTestData = SHOW_TEST_DATA_PREF_DEFAULT;
    private boolean webDevelopmentMode = WEB_DEVELOPMENT_MODE_PREF_DEFAULT;
    @JsonProperty
    public String getCacheDir() {
@@ -72,7 +64,6 @@
    public void copyFrom(ServerConfiguration other) {
        super.copyFrom(other);
        this.port = other.port;
        this.showTestData = other.showTestData;
        this.webDevelopmentMode = other.webDevelopmentMode;
    }
}
borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
@@ -1,11 +1,11 @@
package de.micromata.borgbutler.server.rest;
import de.micromata.borgbutler.BorgCommands;
import de.micromata.borgbutler.DiffTool;
import de.micromata.borgbutler.cache.ButlerCache;
import de.micromata.borgbutler.config.BorgRepoConfig;
import de.micromata.borgbutler.config.ConfigurationHandler;
import de.micromata.borgbutler.data.Archive;
import de.micromata.borgbutler.data.DiffFileSystemFilter;
import de.micromata.borgbutler.data.FileSystemFilter;
import de.micromata.borgbutler.data.Repository;
import de.micromata.borgbutler.json.JsonUtils;
@@ -77,29 +77,30 @@
                                     @QueryParam("diffArchiveId") String diffArchiveId,
                                     @QueryParam("force") boolean force,
                                     @QueryParam("prettyPrinter") boolean prettyPrinter) {
        boolean diffMode = StringUtils.isNotBlank(diffArchiveId);
        int maxSize = NumberUtils.toInt(maxResultSize, 50);
        FileSystemFilter filter = new FileSystemFilter()
                .setSearchString(searchString)
                .setMaxResultSize(maxSize)
                .setMode(mode)
        FileSystemFilter filter = diffMode ? new DiffFileSystemFilter() : new FileSystemFilter();
        filter.setSearchString(searchString)
                .setCurrentDirectory(currentDirectory);
        List<BorgFilesystemItem> items = null;
        if (StringUtils.isBlank(diffArchiveId)) {
        if (diffMode) {
            filter.setMode(FileSystemFilter.Mode.FLAT);
            items = ButlerCache.getInstance().getArchiveContent(archiveId, true, filter);
            List<BorgFilesystemItem> diffItems = ButlerCache.getInstance().getArchiveContent(diffArchiveId, true,
                    filter);
            filter.setMaxResultSize(maxSize)
                    .setMode(mode);
            items = ((DiffFileSystemFilter) filter).extractDifferences(items, diffItems);
            items = filter.reduce(items);
        } else {
            filter.setMode(mode)
                    .setMaxResultSize(maxSize);
            // Get file list (without running diff).
            items = ButlerCache.getInstance().getArchiveContent(archiveId, force,
                    filter);
            if (items == null) {
                return "[{\"mode\": \"notLoaded\"}]";
            }
        } else {
            filter.setMode(FileSystemFilter.Mode.FLAT).setMaxResultSize(-1);
            items = ButlerCache.getInstance().getArchiveContent(archiveId, true, filter);
            List<BorgFilesystemItem> diffItems = ButlerCache.getInstance().getArchiveContent(diffArchiveId, true,
                    filter);
            items = DiffTool.extractDifferences(items, diffItems);
            filter.setMaxResultSize(maxSize)
                    .setMode(mode);
            items = filter.reduce(items);
        }
        return JsonUtils.toJson(items, prettyPrinter);
    }
borgbutler-webapp/src/components/general/IconComponents.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import {
    faBan,
    faCaretDown,
    faCaretUp,
    faCheck,
@@ -8,12 +9,12 @@
    faExclamationTriangle,
    faInfoCircle,
    faPlus,
    faSkullCrossbones,
    faSortDown,
    faSortUp,
    faSync,
    faTrash,
    faTimes,
    faSkullCrossbones,
    faTrash,
    faUpload
} from '@fortawesome/free-solid-svg-icons'
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
@@ -24,6 +25,12 @@
    );
}
function IconBan() {
    return (
        <FontAwesomeIcon icon={faBan}/>
    );
}
function IconCancel() {
    return (
        <FontAwesomeIcon icon={faTimes}/>
@@ -80,7 +87,7 @@
function IconSpinner() {
    return (
        <FontAwesomeIcon icon={faCircleNotch} spin={true} size={'3x'} color={'#aaaaaa'} />
        <FontAwesomeIcon icon={faCircleNotch} spin={true} size={'3x'} color={'#aaaaaa'}/>
    );
}
@@ -110,6 +117,7 @@
export {
    IconAdd,
    IconBan,
    IconCancel,
    IconCheck,
    IconCollapseClose,
borgbutler-webapp/src/components/views/archives/ArchiveView.jsx
@@ -99,7 +99,7 @@
                    <TabPane tabId={'1'}>
                        <FileListPanel
                            repoId={this.state.repoId}
                            archiveId={archive.id}
                            archive={archive}
                            archiveShortInfoList={archive.archiveShortInfoList}
                        />
                    </TabPane>
borgbutler-webapp/src/components/views/archives/FileListEntry.jsx
@@ -2,19 +2,24 @@
import PropTypes from 'prop-types';
import Highlight from 'react-highlighter';
import {Button, UncontrolledTooltip} from 'reactstrap';
import {IconCheck, IconDownload} from '../../general/IconComponents';
import {IconBan, IconCheck, IconDownload} from '../../general/IconComponents';
import {getResponseHeaderFilename, getRestServiceUrl, humanFileSize} from '../../../utilities/global';
import fileDownload from 'js-file-download';
class FileListEntry extends React.Component {
    state = {
        downloaded: false
        thisDownloaded: false,
        otherDownloaded: false
    };
    download(archiveId, fileNumber) {
    download(archiveId, fileNumber, thisDownload) {
        let filename;
        this.setState({downloaded: true});
        if (thisDownload) {
            this.setState({thisDownloaded: true});
        } else {
            this.setState({otherDownloaded: true});
        }
        fetch(getRestServiceUrl('archives/restore', {
            archiveId: archiveId,
            fileNumber: fileNumber
@@ -42,7 +47,6 @@
    render = () => {
        const entry = this.props.entry;
        let downloadArchiveId = this.props.archiveId;
        let displayPath = entry.displayPath;
        let pathCss = 'tt';
@@ -61,14 +65,46 @@
        let mtimeCss = 'tt';
        let mtimeTooltip = undefined;
        let mtimeId = undefined;
        let iconBan = <div className={'btn'}><IconBan/></div>;
        let iconCheck = <div className={'btn'}><IconCheck/></div>;
        let icon1 = iconCheck;
        let icon1Tooltip = '';
        if (!this.state.thisDownloaded) {
            const icon1Id = `icon1-${entry.fileNumber}`;
            icon1 = <div id={icon1Id} className={'btn'}
                         onClick={() => this.download(this.props.archive.id, entry.fileNumber, true)}>
                <IconDownload/></div>;
            icon1Tooltip = <UncontrolledTooltip target={icon1Id}>
                {this.props.archive.time}
            </UncontrolledTooltip>;
        }
        let icon2 = '';
        let icon2Tooltip = '';
        if (this.props.diffArchiveId) {
            icon2 = iconCheck;
            if (!this.state.otherDownloaded) {
                const icon2Id = `icon2-${entry.fileNumber}`;
                icon2 =
                    <div id={icon2Id} className={'btn'}
                         onClick={() => this.download(this.props.diffArchiveId, entry.fileNumber, false)}>
                        <IconDownload/></div>;
                icon2Tooltip = <UncontrolledTooltip target={icon2Id}>
                    other
                </UncontrolledTooltip>;
            }
        }
        if (entry.diffStatus === 'NEW') {
            pathCss = 'tt file-new';
            pathtooltipText = 'NEW';
            icon2 = iconBan;
            icon2Tooltip = '';
        } else if (entry.diffStatus === 'REMOVED') {
            pathCss = 'tt file-removed';
            // Download removed files from other archive.
            downloadArchiveId = this.props.diffArchiveId;
            pathtooltipText = 'REMOVED';
            icon1 = iconBan;
            icon1Tooltip = '';
        } else if (entry.diffStatus === 'MODIFIED') {
            if (entry.differences) {
                pathCss = 'tt file-modified';
@@ -100,7 +136,7 @@
            }
        }
        if (pathtooltipText) {
            pathId = `path-${entry.fileNumber}`;
            pathId = `path-${entry.fileNumber}-${entry.diffStatus}`;
            pathTooltip =
                <UncontrolledTooltip target={pathId}>
                    {pathtooltipText}
@@ -113,16 +149,13 @@
        } else {
            path = <Highlight search={this.props.search} id={pathId}>{displayPath}</Highlight>;
        }
        let icon = this.state.downloaded ? <IconCheck/> :
            <div className={'btn'} onClick={() => this.download(downloadArchiveId, entry.fileNumber)}>
                <IconDownload/></div>;
        return (
            <tr>
                <td className={pathCss}>
                    {path}{pathTooltip}
                </td>
                <td className={'tt'} style={{textAlign: 'center'}}>
                    {icon}
                    {icon1}{icon1Tooltip} {icon2}{icon2Tooltip}
                </td>
                <td className={sizeCss} style={{textAlign: 'center'}}>
                    <span id={sizeId}>{humanFileSize(entry.size, true, true)}</span>{sizeTooltip}
@@ -149,6 +182,7 @@
    entry: PropTypes.shape({}).isRequired,
    search: PropTypes.string,
    mode: PropTypes.string,
    diffArchiveId: PropTypes.string,
    changeCurrentDirectory: PropTypes.func.isRequired
};
borgbutler-webapp/src/components/views/archives/FileListPanel.jsx
@@ -51,7 +51,7 @@
            failed: false
        });
        fetch(getRestServiceUrl('archives/filelist', {
            archiveId: this.props.archiveId,
            archiveId: this.props.archive.id,
            diffArchiveId: this.state.filter.diffArchiveId,
            force: force,
            searchString: this.state.filter.search,
@@ -128,12 +128,12 @@
                            event.preventDefault();
                            this.fetchArchiveFileList();
                        }}
                        currentArchiveId={this.props.archiveId}
                        currentArchiveId={this.props.archive.id}
                        archiveShortInfoList={this.props.archiveShortInfoList}
                    />
                    {breadcrumb}
                    <FileListTable
                        archiveId={this.props.archiveId}
                        archive={this.props.archive}
                        diffArchiveId={this.state.filter.diffArchiveId}
                        entries={this.state.fileList}
                        search={this.state.filter.search}
borgbutler-webapp/src/components/views/archives/FileListTable.jsx
@@ -3,7 +3,7 @@
import {Table} from 'reactstrap';
import FileListEntry from './FileListEntry';
function FileListTable({archiveId, diffArchiveId, entries, search, mode, changeCurrentDirectory}) {
function FileListTable({archive, diffArchiveId, entries, search, mode, changeCurrentDirectory}) {
    const lowercaseSearch = search.split(' ')[0].toLowerCase();
    return (
        <Table striped bordered hover size={'sm'} responsive>
@@ -19,7 +19,7 @@
            <tbody>
            {entries
                .map((entry, index) => <FileListEntry
                    archiveId={archiveId}
                    archive={archive}
                    diffArchiveId={diffArchiveId}
                    entry={entry}
                    search={lowercaseSearch}
@@ -33,7 +33,6 @@
}
FileListTable.propTypes = {
    archiveId: PropTypes.string,
    diffArchiveId: PropTypes.string,
    entries: PropTypes.array,
    search: PropTypes.string,
borgbutler-webapp/src/components/views/config/ConfigurationServerTab.jsx
@@ -45,6 +45,7 @@
            failed: false,
            port: 9042,
            webdevelopmentMode: false,
            showDemoRepos: true,
            maxArchiveContentCacheCapacityMb: 100,
            redirect: false,
            confirmModal: false
@@ -73,7 +74,8 @@
        var config = {
            port: this.state.port,
            maxArchiveContentCacheCapacityMb : this.state.maxArchiveContentCacheCapacityMb,
            webDevelopmentMode: this.state.webDevelopmentMode
            webDevelopmentMode: this.state.webDevelopmentMode,
            showDemoRepos: this.state.showDemoRepos
        };
        return fetch(getRestServiceUrl("configuration/config"), {
            method: 'POST',
@@ -140,6 +142,12 @@
                                         onChange={this.handleTextChange}
                                         placeholder="Enter maximum Capacity"
                                         hint={`Limits the cache size of archive file lists in the local cache directory: ${this.state.cacheDir}`} />
                    <FormLabelField label={'Show demo repositories'} fieldLength={2}>
                        <FormCheckbox checked={this.state.showDemoRepos}
                                      hint={'If true, some demo repositories are shown for testing the functionality of BorgButler without any further configuration and running borg backups.'}
                                      name="showDemoRepos"
                                      onChange={this.handleCheckboxChange} />
                    </FormLabelField>
                    <FormLabelField label={<I18n name={'configuration.webDevelopmentMode'} />} fieldLength={2}>
                        <FormCheckbox checked={this.state.webDevelopmentMode}
                                      hintKey={'configuration.webDevelopmentMode.hint'}
borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
@@ -77,6 +77,56 @@
                    <IconRefresh/>
                </div>
            </React.Fragment>;
            let stats = '';
            if (repo.cache && repo.cache.stats) {
                stats = <tr>
                    <td>Stats</td>
                    <td>
                        <table className="inline">
                            <tbody>
                            <tr>
                                <td>Total chunks</td>
                                <td>{Number(repo.cache.stats.total_chunks).toLocaleString()}</td>
                            </tr>
                            <tr>
                                <td>Total csize</td>
                                <td>{humanFileSize(repo.cache.stats.total_csize)}</td>
                            </tr>
                            <tr>
                                <td>Total size</td>
                                <td>{humanFileSize(repo.cache.stats.total_size)}</td>
                            </tr>
                            <tr>
                                <td>Total unique chunks</td>
                                <td>{Number(repo.cache.stats.total_unique_chunks).toLocaleString()}</td>
                            </tr>
                            <tr>
                                <td>Unique csize</td>
                                <td>{humanFileSize(repo.cache.stats.unique_csize)}</td>
                            </tr>
                            <tr>
                                <td>Unique size</td>
                                <td>{humanFileSize(repo.cache.stats.unique_size)}</td>
                            </tr>
                            </tbody>
                        </table>
                    </td>
                </tr>
            }
            let encryption = '';
            if (repo.encryption) {
                encryption = <tr>
                    <td>Encryption</td>
                    <td>{repo.encryption.mode}</td>
                </tr>
            }
            let cachePath = '';
            if (repo.cache) {
                cachePath = <tr>
                    <td>Cache</td>
                    <td>{repo.cache.path}</td>
                </tr>
            }
            content = <React.Fragment>
                <Nav tabs>
                    <NavLink
@@ -134,51 +184,13 @@
                                <td>Location</td>
                                <td>{repo.location}</td>
                            </tr>
                            <tr>
                                <td>Stats</td>
                                <td>
                                    <table className="inline">
                                        <tbody>
                                        <tr>
                                            <td>Total chunks</td>
                                            <td>{Number(repo.cache.stats.total_chunks).toLocaleString()}</td>
                                        </tr>
                                        <tr>
                                            <td>Total csize</td>
                                            <td>{humanFileSize(repo.cache.stats.total_csize)}</td>
                                        </tr>
                                        <tr>
                                            <td>Total size</td>
                                            <td>{humanFileSize(repo.cache.stats.total_size)}</td>
                                        </tr>
                                        <tr>
                                            <td>Total unique chunks</td>
                                            <td>{Number(repo.cache.stats.total_unique_chunks).toLocaleString()}</td>
                                        </tr>
                                        <tr>
                                            <td>Unique csize</td>
                                            <td>{humanFileSize(repo.cache.stats.unique_csize)}</td>
                                        </tr>
                                        <tr>
                                            <td>Unique size</td>
                                            <td>{humanFileSize(repo.cache.stats.unique_size)}</td>
                                        </tr>
                                        </tbody>
                                    </table>
                                </td>
                            </tr>
                            {stats}
                            <tr>
                                <td>Security dir</td>
                                <td>{repo.securityDir}</td>
                            </tr>
                            <tr>
                                <td>Encryption</td>
                                <td>{repo.encryption.mode}</td>
                            </tr>
                            <tr>
                                <td>Cache</td>
                                <td>{repo.cache.path}</td>
                            </tr>
                            {encryption}
                            {cachePath}
                            </tbody>
                        </Table>
                    </TabPane>