From df12a3a8864d167fb300dd09e645dda430d24164 Mon Sep 17 00:00:00 2001
From: Kai Reinhard <K.Reinhard@micromata.de>
Date: Sat, 15 Dec 2018 16:55:56 +0000
Subject: [PATCH] ...

---
 borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java            |   49 ++++++++-
 borgbutler-webapp/src/components/views/repos/ArchiveView.jsx                       |  101 ++++++++++++++++++++
 borgbutler-webapp/src/containers/WebApp.jsx                                        |    2 
 borgbutler-core/src/main/java/de/micromata/borgbutler/data/Archive.java            |   44 ++++++++
 borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx               |   10 +
 borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java       |   57 +++++++++++
 borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java         |   16 ++-
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java |   19 +++
 8 files changed, 282 insertions(+), 16 deletions(-)

diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
index 988840a..8a89c49 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
@@ -4,10 +4,12 @@
 import de.micromata.borgbutler.config.Configuration;
 import de.micromata.borgbutler.config.ConfigurationHandler;
 import de.micromata.borgbutler.config.Definitions;
+import de.micromata.borgbutler.data.Archive;
 import de.micromata.borgbutler.data.Repository;
 import de.micromata.borgbutler.json.JsonUtils;
 import de.micromata.borgbutler.json.borg.*;
 import de.micromata.borgbutler.utils.DateUtils;
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.exec.CommandLine;
 import org.apache.commons.exec.DefaultExecutor;
 import org.apache.commons.exec.PumpStreamHandler;
@@ -60,6 +62,7 @@
      * The given repository will be cloned and archives will be added.
      * The field {@link Repository#getLastModified()} of masterRepository will be updated.
      *
+     * @param repoConfig       The repo config associated to the masterRepository. Needed for the borg call.
      * @param masterRepository Repository without archives.
      * @return Parsed repo config returned by Borg command including archives.
      */
@@ -89,24 +92,52 @@
     }
 
     /**
-     * Executes borg info archive
+     * Executes borg info repository::archive.
+     * The given repository will be cloned and assigned to the returned archive.
+     * The field {@link Repository#getLastModified()} of masterRepository will be updated.
      *
-     * @param repoConfig
-     * @param archive
-     * @return
+     * @param repoConfig       The repo config associated to the masterRepository. Needed for the borg call.
+     * @param archiveName      The name of the archive returned by {@link BorgArchive#getArchive()}
+     * @param masterRepository Repository without archives.
+     * @return Parsed repo config returned by Borg command including archives.
      */
-    public static BorgArchiveInfo info(BorgRepoConfig repoConfig, String archive) {
-        String json = execute(repoConfig, "info", repoConfig.getRepo() + "::" + archive, "--json");
+    public static Archive info(BorgRepoConfig repoConfig, String archiveName, Repository masterRepository) {
+        String archiveFullname = repoConfig.getRepo() + "::" + archiveName;
+        String json = execute(repoConfig, "info", archiveFullname, "--json");
         if (json == null) {
             return null;
         }
         BorgArchiveInfo archiveInfo = JsonUtils.fromJson(BorgArchiveInfo.class, json);
-        return archiveInfo;
+        if (archiveInfo == null) {
+            log.error("Archive '" + archiveFullname + "' not found.");
+            return null;
+        }
+        masterRepository.setLastModified(DateUtils.format(archiveInfo.getRepository().getLastModified()))
+                .setLastCacheRefresh(DateUtils.format(LocalDateTime.now()));
+        Repository repository = ObjectUtils.clone(masterRepository);
+        Archive archive = new Archive()
+                .setCache(archiveInfo.getCache())
+                .setEncryption(archiveInfo.getEncryption())
+                .setRepository(repository);
+        if (CollectionUtils.isEmpty(archiveInfo.getArchives())) {
+            log.error("The returned borg archive contains no archive infos: " + json);
+            return null;
+        }
+        if (archiveInfo.getArchives().size() > 1   ) {
+            log.warn("Archive '" + archiveFullname + "' contains more than one archives!? (Using only first.)");
+        }
+        BorgArchive2 borgArchive2 = archiveInfo.getArchives().get(0);
+        archive.setStart(DateUtils.format(borgArchive2.getStart()))
+                .setChunkerParams(borgArchive2.getChunkerParams())
+                .setCommandLine(borgArchive2.getCommandLine())
+                .setComment(borgArchive2.getComment())
+                .setStats(borgArchive2.getStats());
+        return archive;
     }
 
-    public static List<BorgFilesystemItem> listArchiveContent(BorgRepoConfig repoConfig, BorgArchive archive) {
+    public static List<BorgFilesystemItem> listArchiveContent(BorgRepoConfig repoConfig, String archiveId) {
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        execute(outputStream, repoConfig, "list", repoConfig.getRepo() + "::" + archive.getArchive(),
+        execute(outputStream, repoConfig, "list", repoConfig.getRepo() + "::" + archiveId,
                 "--json-lines");
         String response = outputStream.toString(Definitions.STD_CHARSET);
         List<BorgFilesystemItem> content = new ArrayList<>();
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
index fe2fa22..bfd1cd2 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -4,6 +4,7 @@
 import de.micromata.borgbutler.config.BorgRepoConfig;
 import de.micromata.borgbutler.config.Configuration;
 import de.micromata.borgbutler.config.ConfigurationHandler;
+import de.micromata.borgbutler.data.Archive;
 import de.micromata.borgbutler.data.Repository;
 import de.micromata.borgbutler.json.borg.BorgArchive;
 import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
@@ -26,6 +27,7 @@
     private JCSCache jcsCache;
     private CacheAccess<String, Repository> repoCacheAccess;
     private CacheAccess<String, Repository> repoArchivesCacheAccess;
+    private CacheAccess<String, Archive> archivesCacheAccess;
     private ArchiveFilelistCache archiveFilelistCache;
 
     public static ButlerCache getInstance() {
@@ -89,6 +91,8 @@
         clearRepoArchicesCacheAccess();
         log.info("Clearing cache with file lists of archives...");
         this.archiveFilelistCache.removeAllCacheFiles();
+        log.info("Clearing archives cache...");
+        this.archivesCacheAccess.clear();
     }
 
     public void clearRepoCacheAccess() {
@@ -121,13 +125,63 @@
         return repository;
     }
 
+    public Archive getArchive(String repoName, String archiveIdOrName) {
+        return getArchive(repoName, archiveIdOrName, false);
+    }
+
+    /**
+     * @param repoName
+     * @param archiveIdOrName
+     * @param forceReload     If true, any cache value will be ignored. Default is false.
+     * @return
+     */
+    public Archive getArchive(String repoName, String archiveIdOrName, boolean forceReload) {
+        BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(repoName);
+        if (repoConfig == null) {
+            log.error("Can't find configured repo '" + repoName + "'.");
+            return null;
+        }
+        return getArchive(repoConfig, archiveIdOrName, forceReload);
+    }
+
+    public Archive getArchive(BorgRepoConfig repoConfig, String archiveIdOrName, boolean forceReload) {
+        Repository masterRepository = getRepositoryArchives(repoConfig.getRepo());
+        if (masterRepository == null) {
+            log.error("Repository '" + repoConfig.getRepo() + "' not found.");
+            return null;
+        }
+        String archiveName = archiveIdOrName;
+        if (CollectionUtils.isEmpty(masterRepository.getArchives())) {
+            log.warn("Repository '" + repoConfig.getRepo() + "' doesn't contain archives.");
+        } else {
+            for (BorgArchive borgArchive : masterRepository.getArchives()) {
+                if (StringUtils.equals(borgArchive.getArchive(), archiveIdOrName)
+                        || StringUtils.equals(borgArchive.getId(), archiveIdOrName)) {
+                    archiveName = borgArchive.getArchive();
+                    break;
+                }
+            }
+        }
+        String archiveFullname = repoConfig.getRepo() + "::" + archiveName;
+        if (!forceReload) {
+            Archive archive = this.archivesCacheAccess.get(archiveFullname);
+            if (archive != null) {
+                return archive;
+            }
+        }
+        Archive archive = BorgCommands.info(repoConfig, archiveName, masterRepository);
+        if (archive != null)
+            this.archivesCacheAccess.put(archiveFullname, archive);
+        return archive;
+    }
+
     public BorgFilesystemItem[] getArchiveContent(BorgRepoConfig repoConfig, BorgArchive archive) {
         if (archive == null || StringUtils.isBlank(archive.getArchive())) {
             return null;
         }
         BorgFilesystemItem[] items = archiveFilelistCache.load(repoConfig, archive);
         if (items == null) {
-            List<BorgFilesystemItem> list = BorgCommands.listArchiveContent(repoConfig, archive);
+            List<BorgFilesystemItem> list = BorgCommands.listArchiveContent(repoConfig, archive.getArchive());
             if (CollectionUtils.isNotEmpty(list)) {
                 archiveFilelistCache.save(repoConfig, archive, list);
                 items = list.toArray(new BorgFilesystemItem[0]);
@@ -156,6 +210,7 @@
         this.jcsCache = JCSCache.getInstance();
         this.repoCacheAccess = jcsCache.getJCSCache("repositories");
         this.repoArchivesCacheAccess = jcsCache.getJCSCache("repositoriesArchives");
+        this.archivesCacheAccess = jcsCache.getJCSCache("archives");
         this.archiveFilelistCache = new ArchiveFilelistCache(getCacheDir(), configuration.getCacheArchiveContentMaxDiscSizeMB());
     }
 }
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/data/Archive.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/data/Archive.java
new file mode 100644
index 0000000..14c6f7b
--- /dev/null
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/data/Archive.java
@@ -0,0 +1,44 @@
+package de.micromata.borgbutler.data;
+
+import de.micromata.borgbutler.json.borg.BorgArchiveStats;
+import de.micromata.borgbutler.json.borg.BorgCache;
+import de.micromata.borgbutler.json.borg.BorgEncryption;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+/**
+ *
+ */
+public class Archive implements Serializable {
+    @Getter
+    @Setter
+    private Repository repository;
+    @Getter
+    @Setter
+    private BorgCache cache;
+    @Getter
+    @Setter
+    private BorgEncryption encryption;
+
+    @Getter
+    @Setter
+    private int[] chunkerParams;
+    /**
+     * The command line used for creating this archive: borg create --filter...
+     */
+    @Getter
+    @Setter
+    private String[] commandLine;
+    @Getter
+    @Setter
+    private String comment;
+    @Getter
+    @Setter
+    private String start;
+    @Getter
+    @Setter
+    private BorgArchiveStats stats;
+
+}
diff --git a/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java b/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java
index d5fdcc4..1a600e8 100644
--- a/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java
+++ b/borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java
@@ -3,6 +3,7 @@
 import de.micromata.borgbutler.config.BorgRepoConfig;
 import de.micromata.borgbutler.config.Configuration;
 import de.micromata.borgbutler.config.ConfigurationHandler;
+import de.micromata.borgbutler.data.Archive;
 import de.micromata.borgbutler.data.Repository;
 import de.micromata.borgbutler.json.borg.BorgArchive;
 import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
@@ -14,6 +15,7 @@
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 
 public class CacheTest {
@@ -41,20 +43,24 @@
             assertEquals(config.getRepoConfigs().size(), ButlerCache.getInstance().getAllRepositories().size());
         }
         List<BorgRepoConfig> repoConfigs = ConfigurationHandler.getConfiguration().getRepoConfigs();
-        BorgArchive archive = null;
+        BorgArchive borgArchive = null;
         BorgRepoConfig repoConfig = null;
         if (CollectionUtils.isNotEmpty(repoConfigs)) {
             repoConfig = repoConfigs.get(0);
             Repository rerepositoryoList = ButlerCache.getInstance().getRepositoryArchives(repoConfig.getRepo());
             if (rerepositoryoList != null && CollectionUtils.isNotEmpty(rerepositoryoList.getArchives())) {
-                archive = rerepositoryoList.getArchives().get(0);
+                borgArchive = rerepositoryoList.getArchives().first();
             }
         }
         {
-            if (archive != null) {
-                BorgFilesystemItem[] content = ButlerCache.getInstance().getArchiveContent(repoConfig, archive);
+            if (borgArchive != null) {
+                Archive archive = ButlerCache.getInstance().getArchive(repoConfig.getRepo(), borgArchive.getName());
+                assertNotNull(archive);
+                archive = ButlerCache.getInstance().getArchive(repoConfig.getRepo(), borgArchive.getId());
+                assertNotNull(archive);
+                BorgFilesystemItem[] content = ButlerCache.getInstance().getArchiveContent(repoConfig, borgArchive);
                 log.info("Number of items (content) of archive: " + content.length);
-                content = ButlerCache.getInstance().getArchiveContent(repoConfig, archive);
+                content = ButlerCache.getInstance().getArchiveContent(repoConfig, borgArchive);
                 log.info("Number of items (content) of archive: " + content.length);
             }
         }
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
index 66d914e..fa2bca9 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
@@ -1,6 +1,7 @@
 package de.micromata.borgbutler.server.rest;
 
 import de.micromata.borgbutler.cache.ButlerCache;
+import de.micromata.borgbutler.data.Archive;
 import de.micromata.borgbutler.data.Repository;
 import de.micromata.borgbutler.json.JsonUtils;
 import de.micromata.borgbutler.json.borg.BorgRepository;
@@ -55,6 +56,24 @@
     }
 
     @GET
+    @Path("archive")
+    @Produces(MediaType.APPLICATION_JSON)
+    /**
+     *
+     * @param repo Name of repository ({@link Repository#getName()}.
+     * @param archive Id or name of archive.
+     * @param prettyPrinter If true then the json output will be in pretty format.
+     * @return Repository (including list of archives) as json string.
+     * @see JsonUtils#toJson(Object, boolean)
+     */
+    public String getArchive(@QueryParam("repo") String repoName,
+                             @QueryParam("archive") String archiveIdOrName, @QueryParam("force") boolean force,
+                             @QueryParam("prettyPrinter") boolean prettyPrinter) {
+        Archive archive = ButlerCache.getInstance().getArchive(repoName, archiveIdOrName, force);
+        return JsonUtils.toJson(archive, prettyPrinter);
+    }
+
+    @GET
     @Path("list")
     @Produces(MediaType.APPLICATION_JSON)
     /**
diff --git a/borgbutler-webapp/src/components/views/repos/ArchiveView.jsx b/borgbutler-webapp/src/components/views/repos/ArchiveView.jsx
new file mode 100644
index 0000000..ad889b3
--- /dev/null
+++ b/borgbutler-webapp/src/components/views/repos/ArchiveView.jsx
@@ -0,0 +1,101 @@
+import React from 'react'
+import {Table} from 'reactstrap';
+import {PageHeader} from '../../general/BootstrapComponents';
+import {getRestServiceUrl} from '../../../utilities/global';
+import ErrorAlert from '../../general/ErrorAlert';
+import {IconRefresh} from "../../general/IconComponents";
+
+class ArchiveView extends React.Component {
+
+    state = {
+        repo: this.props.match.params.repoId,
+        archive: this.props.match.params.archiveId,
+        isFetching: false,
+        activeTab: '1',
+    };
+
+    componentDidMount = () => {
+        this.fetchArchive();
+    };
+
+
+    fetchArchive = (force) => {
+        this.setState({
+            isFetching: true,
+            failed: false
+        });
+        fetch(getRestServiceUrl('repos/archive', {
+            repo: this.state.repo,
+            archive: this.state.archive,
+            force: force
+        }), {
+            method: 'GET',
+            headers: {
+                'Accept': 'application/json'
+            }
+        })
+            .then(response => response.json())
+            .then(json => {
+                this.setState({
+                    isFetching: false,
+                    repo: json
+                })
+            })
+            .catch(() => this.setState({isFetching: false, failed: true}));
+    };
+
+    render = () => {
+        let content = undefined;
+        const repo = this.state.repo;
+        let pageHeader = '';
+
+        if (this.state.isFetching) {
+            content = <i>Loading...</i>;
+        } else if (this.state.failed) {
+            content = <ErrorAlert
+                title={'Cannot load Repositories'}
+                description={'Something went wrong during contacting the rest api.'}
+                action={{
+                    handleClick: this.fetchArchive,
+                    title: 'Try again'
+                }}
+            />;
+        } else if (this.state.repo) {
+            pageHeader = <React.Fragment>
+                {repo.displayName}
+                <div
+                    className={'btn btn-outline-primary refresh-button-right'}
+                    onClick={this.fetchArchive.bind(this, true)}
+                >
+                    <IconRefresh/>
+                </div>
+            </React.Fragment>;
+            content = <React.Fragment>
+                <Table hover>
+                    <tbody>
+                    <tr>
+                        <th>Archive</th>
+                        <th>Time</th>
+                        <th>Id</th>
+                    </tr>
+                    </tbody>
+                </Table>
+            </React.Fragment>;
+
+        }
+        return <React.Fragment>
+            <PageHeader>
+                {pageHeader}
+            </PageHeader>
+            {content}
+        </React.Fragment>;
+    };
+
+    constructor(props) {
+        super(props);
+
+        this.fetchArchive = this.fetchArchive.bind(this);
+    }
+}
+
+export default ArchiveView;
diff --git a/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx b/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
index 03eed27..ffebf88 100644
--- a/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
+++ b/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
@@ -49,6 +49,13 @@
         })
     };
 
+    redirectToArchive = (archive) => {
+        //console.log("Archive: " + archive.id + ", repo: " + this.state.repo.id);
+        console.log("context: " + this.context.history);
+
+        this.context.history.pushState(null, '/archives');
+        //this.router.transitionTo('/archives/dfsafds'); // , {repoId: this.state.repo.id}, {archiveId: archive.id}
+    }
 
     render = () => {
         let content = undefined;
@@ -103,7 +110,7 @@
                             {repo.archives.map((archive) => {
                                 // Return the element. Also pass key
                                 return (
-                                    <tr key={archive.id}>
+                                    <tr key={archive.id} onClick={() => this.redirectToArchive(archive)}>
                                         <td>{archive.archive}</td>
                                         <td>{archive.time}</td>
                                         <td>{archive.id}</td>
@@ -192,6 +199,7 @@
 
         this.fetchRepo = this.fetchRepo.bind(this);
         this.toggleTab = this.toggleTab.bind(this);
+        this.redirectToArchive = this.redirectToArchive.bind(this);
     }
 }
 
diff --git a/borgbutler-webapp/src/containers/WebApp.jsx b/borgbutler-webapp/src/containers/WebApp.jsx
index a134531..0582496 100644
--- a/borgbutler-webapp/src/containers/WebApp.jsx
+++ b/borgbutler-webapp/src/containers/WebApp.jsx
@@ -7,6 +7,7 @@
 import Start from '../components/views/Start';
 import RepoListView from '../components/views/repos/RepoListView';
 import RepoArchiveListView from '../components/views/repos/RepoArchiveListView';
+import ArchiveView from '../components/views/repos/ArchiveView';
 import ConfigurationPage from '../components/views/config/ConfigurationPage';
 import RestServices from '../components/views/develop/RestServices';
 import {isDevelopmentMode} from '../utilities/global';
@@ -52,6 +53,7 @@
                                 ))
                             }
                             <Route path={'/repoArchives/:id'} component={RepoArchiveListView}/>
+                            <Route path={'/archives/:repoId/:archiveId'} component={ArchiveView}/>
                         </Switch>
                     </div>
                     <Footer versionInfo={this.props.version}/>

--
Gitblit v1.10.0