From c6e77f6fa462e292db5f693a33e7c483b5a6e19e Mon Sep 17 00:00:00 2001
From: Kai Reinhard <K.Reinhard@micromata.de>
Date: Fri, 16 Apr 2021 23:59:02 +0000
Subject: [PATCH] Release 0.5 started: SpringBoot instead of Jetty, Jersey etc.
---
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/ServerConfiguration.kt | 50 +
borgbutler-core/build.gradle | 16
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RequestLog.kt | 138 +++
borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Configuration.kt | 4
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfo.kt | 33
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationInfo.kt | 9
borgbutler-webapp/src/components/views/archives/FileListPanel.jsx | 2
borgbutler-core/src/main/kotlin/de/micromata/borgbutler/json/JsonUtils.kt | 69 +
borgbutler-server/build.gradle | 22
borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LoggingEventData.java | 26
build.gradle | 13
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfoRest.kt | 26
doc/Development.adoc | 4
borgbutler-docker/app/Dockerfile | 2
borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java | 5
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/WebConfig.kt | 30
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/I18nRest.kt | 42 +
borgbutler-core/src/main/kotlin/de/micromata/borgbutler/BorgJob.kt | 4
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/logging/LoggerMemoryAppender.kt | 134 +++
borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java | 6
borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/SingleUserManager.java | 6
borgbutler-webapp/src/components/views/archives/ArchiveView.jsx | 2
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.kt | 141 ++++
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ReposRest.kt | 66 +
borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx | 2
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ArchivesRest.kt | 221 ++++++
borgbutler-server/src/main/resources/application.properties | 2
/dev/null | 21
borgbutler-docker/app/entrypoint.sh | 2
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/GlobalDefaultExceptionHandling.kt | 57 +
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/JobsRest.kt | 201 +++++
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/BorgButlerApplication.kt | 179 +++++
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationRest.kt | 76 ++
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ExceptionStackTracePrinter.kt | 72 ++
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/VersionRest.kt | 66 +
borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/ConfigurationHandler.kt | 8
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt | 112 +++
borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LogLevel.java | 8
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/LoggingRest.kt | 67 +
borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.kt | 82 ++
borgbutler-server/src/main/resources/logback-spring.xml | 35 +
41 files changed, 1,974 insertions(+), 87 deletions(-)
diff --git a/borgbutler-core/build.gradle b/borgbutler-core/build.gradle
index 7a89458..f7a81d5 100644
--- a/borgbutler-core/build.gradle
+++ b/borgbutler-core/build.gradle
@@ -1,6 +1,5 @@
plugins {
id 'java'
- id "org.jetbrains.kotlin.jvm" version "1.4.32"
}
description = 'borgbutler-core'
@@ -10,10 +9,10 @@
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
implementation group: 'org.apache.commons', name: 'commons-exec', version: '1.3'
implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.2'
- implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.18'
+ implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.20'
implementation group: 'org.apache.commons', name: 'commons-jcs-core', version: '2.2.1'
// https://mvnrepository.com/artifact/com.esotericsoftware/kryo
- implementation group: 'com.esotericsoftware', name: 'kryo', version: '5.0.0-RC1'
+ implementation group: 'com.esotericsoftware', name: 'kryo', version: '5.1.0'
// Serialization (faster than Java built-in)
implementation 'io.github.microutils:kotlin-logging-jvm:2.0.6'
@@ -32,7 +31,6 @@
}
sourceSets {
- main.kotlin.srcDirs += 'src/main/kotlin'
main.java.srcDirs += 'src/main/java'
}
@@ -41,13 +39,3 @@
minHeapSize = "128m"
maxHeapSize = "1500m"
}
-compileKotlin {
- kotlinOptions {
- jvmTarget = "1.8"
- }
-}
-compileTestKotlin {
- kotlinOptions {
- jvmTarget = "1.9"
- }
-}
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
index a786023..ac82d24 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
@@ -1,6 +1,7 @@
package de.micromata.borgbutler.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
+import de.micromata.borgbutler.json.JsonUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
@@ -93,4 +94,9 @@
public void setId(String id) {
this.id = id;
}
+
+ @Override
+ public String toString() {
+ return JsonUtils.toJson(this);
+ }
}
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/json/JsonUtils.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/json/JsonUtils.java
deleted file mode 100644
index 5263f72..0000000
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/json/JsonUtils.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package de.micromata.borgbutler.json;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.core.io.JsonStringEncoder;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.io.StringWriter;
-
-public class JsonUtils {
- private static Logger log = LoggerFactory.getLogger(JsonUtils.class);
-
- public static String toJson(Object obj) {
- return toJson(obj, false);
- }
-
- /**
- * @param obj
- * @param prettyPrinter If true, the json output will be pretty printed (human readable with new lines and indenting).
- * @return
- */
- public static String toJson(Object obj, boolean prettyPrinter) {
- if (obj == null) {
- return "";
- }
- ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
- try {
- if (prettyPrinter) {
- return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
- } else {
- StringWriter writer = new StringWriter();
- objectMapper.writeValue(writer, obj);
- return writer.toString();
- }
- } catch (IOException ex) {
- log.error(ex.getMessage(), ex);
- return "";
- }
- }
-
- public static String toJson(String str) {
- if (str == null) return "";
- return new String(JsonStringEncoder.getInstance().quoteAsString(str));
- }
-
- public static <T> T fromJson(Class<T> clazz, String json) {
- ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- try {
- return objectMapper.readValue(json, clazz);
- } catch (IOException ex) {
- log.error(ex.getMessage(), ex);
- return null;
- }
- }
-
- public static <T> T fromJson(final TypeReference<T> type, final String json) {
- try {
- T data = new ObjectMapper().readValue(json, type);
- return data;
- } catch (Exception ex) {
- log.error("Json: '" + json + "': " + ex.getMessage(), ex);
- }
- return null;
- }
-}
diff --git a/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/BorgJob.kt b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/BorgJob.kt
index 6f41b00..ca5d6c6 100644
--- a/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/BorgJob.kt
+++ b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/BorgJob.kt
@@ -6,18 +6,20 @@
import de.micromata.borgbutler.jobs.JobResult
import de.micromata.borgbutler.json.JsonUtils
import de.micromata.borgbutler.json.borg.ProgressInfo
+import mu.KotlinLogging
import org.apache.commons.exec.CommandLine
import org.apache.commons.exec.environment.EnvironmentUtils
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory
import java.io.IOException
+private val log = KotlinLogging.logger {}
+
/**
* A queue is important because Borg doesn't support parallel calls for one repository.
* For each repository one single queue is allocated.
*/
open class BorgJob<T> : AbstractCommandLineJob, Cloneable {
- private val log = LoggerFactory.getLogger(BorgJob::class.java)
var command: BorgCommand? = null
private set
diff --git a/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Configuration.kt b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Configuration.kt
index 7eb3cbf..d8781f5 100644
--- a/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Configuration.kt
+++ b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Configuration.kt
@@ -4,15 +4,17 @@
import com.fasterxml.jackson.annotation.JsonProperty
import de.micromata.borgbutler.config.BorgRepoConfig
import de.micromata.borgbutler.demo.DemoRepos
+import mu.KotlinLogging
import org.apache.commons.lang3.StringUtils
import org.slf4j.LoggerFactory
import java.io.File
+private val log = KotlinLogging.logger {}
+
/**
* Representation of ~/.borgbutler/borgbutler-config.json.
*/
open class Configuration {
- private val log = LoggerFactory.getLogger(Configuration::class.java)
@JsonIgnore
private var workingDir: File? = null
diff --git a/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/ConfigurationHandler.kt b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/ConfigurationHandler.kt
index 2715560..d4f9444 100644
--- a/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/ConfigurationHandler.kt
+++ b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/ConfigurationHandler.kt
@@ -101,14 +101,14 @@
}
@kotlin.jvm.JvmStatic
- fun getInstance(): ConfigurationHandler? {
+ fun getInstance(): ConfigurationHandler {
if (instance == null) instance = ConfigurationHandler()
- return instance
+ return instance!!
}
@kotlin.jvm.JvmStatic
- fun getConfiguration(): Configuration? {
- return getInstance()!!.configuration
+ fun getConfiguration(): Configuration {
+ return getInstance().configuration!!
}
@kotlin.jvm.JvmStatic
diff --git a/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/json/JsonUtils.kt b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/json/JsonUtils.kt
new file mode 100644
index 0000000..54864d7
--- /dev/null
+++ b/borgbutler-core/src/main/kotlin/de/micromata/borgbutler/json/JsonUtils.kt
@@ -0,0 +1,69 @@
+package de.micromata.borgbutler.json
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.core.io.JsonStringEncoder
+import com.fasterxml.jackson.core.type.TypeReference
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.ObjectMapper
+import mu.KotlinLogging
+import org.slf4j.LoggerFactory
+import java.io.IOException
+import java.io.StringWriter
+
+private val log = KotlinLogging.logger {}
+
+object JsonUtils {
+ /**
+ * @param obj
+ * @param prettyPrinter If true, the json output will be pretty printed (human readable with new lines and indenting).
+ * @return
+ */
+ @JvmOverloads
+ @JvmStatic
+ fun toJson(obj: Any?, prettyPrinter: Boolean? = false): String {
+ if (obj == null) {
+ return ""
+ }
+ val objectMapper = ObjectMapper()
+ objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
+ return try {
+ if (prettyPrinter == true) {
+ objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj)
+ } else {
+ val writer = StringWriter()
+ objectMapper.writeValue(writer, obj)
+ writer.toString()
+ }
+ } catch (ex: IOException) {
+ log.error(ex.message, ex)
+ ""
+ }
+ }
+
+ @JvmStatic
+ fun toJson(str: String?): String {
+ return if (str == null) "" else String(JsonStringEncoder.getInstance().quoteAsString(str))
+ }
+
+ @JvmStatic
+ fun <T> fromJson(clazz: Class<T>?, json: String?): T? {
+ val objectMapper = ObjectMapper()
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ return try {
+ objectMapper.readValue(json, clazz)
+ } catch (ex: IOException) {
+ log.error(ex.message, ex)
+ null
+ }
+ }
+
+ @JvmStatic
+ fun <T> fromJson(type: TypeReference<T>?, json: String): T? {
+ try {
+ return ObjectMapper().readValue(json, type)
+ } catch (ex: Exception) {
+ log.error("Json: '" + json + "': " + ex.message, ex)
+ }
+ return null
+ }
+}
diff --git a/borgbutler-docker/app/Dockerfile b/borgbutler-docker/app/Dockerfile
index 8ba9f29..a5b9096 100644
--- a/borgbutler-docker/app/Dockerfile
+++ b/borgbutler-docker/app/Dockerfile
@@ -16,7 +16,7 @@
USER borgbutler:borgbutler
# Don't put fat jar files in docker images: https://phauer.com/2019/no-fat-jar-in-docker-image/
-ARG DEPENDENCY=target/dependency/borgbutler-server-0.4-SNAPSHOT
+ARG DEPENDENCY=target/dependency/borgbutler-server-0.5-SNAPSHOT
COPY ${DEPENDENCY}/lib /app/lib
COPY ${DEPENDENCY}/web /app/web
#COPY ${DEPENDENCY}/META-INF /app/META-INF
diff --git a/borgbutler-docker/app/entrypoint.sh b/borgbutler-docker/app/entrypoint.sh
index a969e6d..0d6e59a 100644
--- a/borgbutler-docker/app/entrypoint.sh
+++ b/borgbutler-docker/app/entrypoint.sh
@@ -49,7 +49,7 @@
echo "Starting java ${JAVA_OPTS} -cp app/web/*:app/lib/* -DborgbutlerHome=/BorgButler/ -DapplicationHome=/app -DbindAddress=0.0.0.0 -DallowedClientIps=172.17. de.micromata.borgbutler.server.Main -q ${JAVA_ARGS}"
-java ${JAVA_OPTS} -cp app/web/*:app/lib/* -DborgbutlerHome=/BorgButler/ -DapplicationHome=/app -DbindAddress=0.0.0.0 -DallowedClientIps=172.17. de.micromata.borgbutler.server.Main -q ${JAVA_ARGS} &
+java $JAVA_OPTS -cp app/web/*:app/lib/* -DborgbutlerHome=/BorgButler/ -DapplicationHome=/app -DbindAddress=0.0.0.0 -DallowedClientIps=172.17. de.micromata.borgbutler.server.Main -q $JAVA_ARGS &
CHILD=$!
wait $CHILD
diff --git a/borgbutler-server/build.gradle b/borgbutler-server/build.gradle
index 62e4864..470ee6f 100644
--- a/borgbutler-server/build.gradle
+++ b/borgbutler-server/build.gradle
@@ -10,6 +10,7 @@
plugins {
id 'java'
+ id 'org.springframework.boot' version '2.4.5'
}
description = 'borgbutler-server'
@@ -21,20 +22,13 @@
implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.2'
implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.18'
implementation group: 'commons-io', name: 'commons-io', version: '2.8.0'
- implementation group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.4.12.v20180830'
- implementation group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.4.12.v20180830'
- implementation group: 'org.eclipse.jetty', name: 'jetty-servlets', version: '9.4.12.v20180830'
- implementation group: 'org.glassfish.jaxb', name: 'jaxb-core', version: '2.3.0.1'
- implementation group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '2.3.1'
- implementation group: 'org.glassfish.jersey.containers', name: 'jersey-container-servlet', version: '2.27'
- implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-multipart', version: '2.27'
- implementation group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '2.27'
- implementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '2.27'
- implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'
- implementation group: 'javax.xml.ws', name: 'jaxws-api', version: '2.3.1'
- implementation group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25'
+
+ // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web
+ implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.4.5'
+ implementation 'io.github.microutils:kotlin-logging-jvm:2.0.6'
+
// https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient
- implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.6'
+ implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13'
// https://mvnrepository.com/artifact/commons-cli/commons-cli
implementation group: 'commons-cli', name: 'commons-cli', version: '1.4'
@@ -49,7 +43,7 @@
apply plugin: 'application'
apply plugin: 'kotlin'
-mainClassName = "de.micromata.borgbutler.server.Main"
+mainClassName = "de.micromata.borgbutler.server.BorgButlerApplication"
run() {
doFirst {
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
deleted file mode 100644
index 036d7fe..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
+++ /dev/null
@@ -1,189 +0,0 @@
-package de.micromata.borgbutler.server;
-
-import de.micromata.borgbutler.cache.ButlerCache;
-import de.micromata.borgbutler.config.ConfigurationHandler;
-import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
-import de.micromata.borgbutler.server.jetty.JettyServer;
-import de.micromata.borgbutler.server.user.SingleUserManager;
-import de.micromata.borgbutler.server.user.UserManager;
-import org.apache.commons.cli.*;
-import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.awt.*;
-import java.io.*;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-import java.util.TimeZone;
-
-public class Main {
- private static Logger log = LoggerFactory.getLogger(Main.class);
-
- private static final Main main = new Main();
-
- private JettyServer server;
- private boolean shutdownInProgress;
-
- private Main() {
- }
-
- public static void main(String[] args) {
- main._start(args);
- }
-
- public static JettyServer startUp(String... restPackageNames) {
- return main._startUp(restPackageNames);
- }
-
- public static void shutdown() {
- main._shutdown();
- }
-
-
- private void _start(String[] args) {
- ConfigurationHandler.setConfigClazz(ServerConfiguration.class);
- // create Options object
- Options options = new Options();
- options.addOption("e", "extract-archive-content", true, "Extracts the content of an archive cache file only (doesn't start the server). A complete file list of the archive will be extracted to stdout.");
- options.addOption("p", "port", true, "The default port for the web server.");
- options.addOption("q", "quiet", false, "Don't open browser automatically.");
- options.addOption("h", "help", false, "Print this help screen.");
- //options.addOption("homeDir", true, "Specify own home directory of butler. Default is $HOME/.borgbutler");
- CommandLineParser parser = new DefaultParser();
- try {
- // parse the command line arguments
- CommandLine line = parser.parse(options, args);
- if (line.hasOption('h')) {
- printHelp(options);
- return;
- }
- if (line.hasOption('e')) {
- String file = line.getOptionValue("e");
- printArchiveContent(file);
- return;
- }
- if (line.hasOption('p')) {
- // initialise the member variable
- String portString = line.getOptionValue("p");
- try {
- int port = Integer.parseInt(portString);
- if (port < 1 || port > 65535) {
- System.err.println("Port outside range.");
- return;
- }
- ServerConfiguration.get().setPort(port);
- } catch (NumberFormatException ex) {
- printHelp(options);
- return;
- }
- }
- String applicationHome = System.getProperty("borgbutlerHome");
- if (applicationHome != null) {
- ConfigurationHandler.init(applicationHome);
- }
- if (Desktop.isDesktopSupported()) {
- RunningMode.setServerType(RunningMode.ServerType.DESKTOP);
- } else {
- RunningMode.setServerType(RunningMode.ServerType.SERVER);
- }
- RunningMode.logMode();
- Runtime.getRuntime().addShutdownHook(new Thread() {
- @Override
- public void run() {
- main._shutdown();
- }
- });
-
- JettyServer server = startUp();
- BorgInstallation.getInstance().initialize();
- if (!line.hasOption('q')) {
-
- try {
- java.awt.Desktop.getDesktop().browse(java.net.URI.create(server.getUrl()));
- } catch (Exception ex) {
- log.info("Can't open web browser: " + ex.getMessage());
- }
- } else {
- log.info("Please open your browser: " + server.getUrl().replace("0.0.0.0", "127.0.0.1")); // 0.0.0.0 for Docker installations.
- }
- } catch (ParseException ex) {
- // oops, something went wrong
- System.err.println("Parsing failed. Reason: " + ex.getMessage());
- printHelp(options);
- }
- }
-
- private JettyServer _startUp(String... restPackageNames) {
- server = new JettyServer();
- server.start(restPackageNames);
-
- UserManager.setUserManager(new SingleUserManager());
-
- return server;
- }
-
- private void _shutdown() {
- if (server == null) {
- // Do nothing (server wasn't started).
- return;
- }
- synchronized (this) {
- if (shutdownInProgress == true) {
- // Another thread already called this method. There is nothing further to do.
- return;
- }
- shutdownInProgress = true;
- }
- log.info("Shutting down BorgButler web server...");
- server.stop();
- ButlerCache.getInstance().shutdown();
- }
-
- private static void printHelp(Options options) {
- HelpFormatter formatter = new HelpFormatter();
- formatter.printHelp("borgbutler-server", options);
- }
-
- private static void printArchiveContent(String fileName) {
- File file = new File(fileName);
- List<BorgFilesystemItem> fileList = ButlerCache.getInstance().getArchiveContent(file);
- boolean parseFormatExceptionPrinted = false;
- if (fileList != null && fileList.size() > 0) {
- TimeZone tz = TimeZone.getTimeZone("UTC");
- DateFormat iso = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'"); // Quoted "Z" to indicate UTC, no timezone offset
- iso.setTimeZone(tz);
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S");
- 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) {
- String time = item.getMtime();
- if (time.indexOf('T') > 0) {
- try {
- Date date = df.parse(item.getMtime());
- time = iso.format(date);
- } catch (java.text.ParseException ex) {
- if (!parseFormatExceptionPrinted) {
- parseFormatExceptionPrinted = true;
- log.error("Can't parse date: " + item.getMtime());
- }
- }
- }
- writer.write(item.getMode() + " " + item.getUser() + " "
- + StringUtils.rightPad(FileUtils.byteCountToDisplaySize(item.getSize()), 10)
- + " " + time + " " + item.getPath());
- writer.write("\n");
- }
- } catch (IOException ex) {
- log.error("Can't write file '" + out.getAbsolutePath() + "': " + ex.getMessage());
- }
- }
- // 2018-12-04T22:44:58.924642
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java
deleted file mode 100644
index 8e24c4e..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package de.micromata.borgbutler.server;
-
-import org.apache.commons.lang3.StringUtils;
-import de.micromata.borgbutler.config.Configuration;
-import de.micromata.borgbutler.config.ConfigurationHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class ServerConfiguration extends Configuration {
- 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 WEB_DEVELOPMENT_MODE_PREF_DEFAULT = false;
-
- private static String applicationHome;
-
- private int port = WEBSERVER_PORT_DEFAULT;
- private boolean webDevelopmentMode = WEB_DEVELOPMENT_MODE_PREF_DEFAULT;
-
- public static ServerConfiguration get() {
- return (ServerConfiguration)ConfigurationHandler.getConfiguration();
- }
-
- public static String[] getSupportedLanguages() {
- return SUPPORTED_LANGUAGES;
- }
-
- public static String getApplicationHome() {
- if (applicationHome == null) {
- applicationHome = System.getProperty("applicationHome");
- if (StringUtils.isBlank(applicationHome)) {
- applicationHome = System.getProperty("user.dir");
- log.info("applicationHome is not given as JVM parameter. Using current working dir (OK for start in IDE): " + applicationHome);
- }
- }
- return applicationHome;
- }
-
- public int getPort() {
- return port;
- }
-
- public void setPort(int port) {
- this.port = port;
- }
-
- /**
- * If true, CrossOriginFilter will be set.
- */
- public boolean isWebDevelopmentMode() {
- return webDevelopmentMode;
- }
-
- public void setWebDevelopmentMode(boolean webDevelopmentMode) {
- this.webDevelopmentMode = webDevelopmentMode;
- }
-
- public void copyFrom(ServerConfiguration other) {
- super.copyFrom(other);
- this.port = other.port;
- this.webDevelopmentMode = other.webDevelopmentMode;
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java
deleted file mode 100644
index 5f9c29c..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java
+++ /dev/null
@@ -1,188 +0,0 @@
-package de.micromata.borgbutler.server.jetty;
-
-import de.micromata.borgbutler.server.ServerConfiguration;
-import de.micromata.borgbutler.server.RunningMode;
-import de.micromata.borgbutler.server.rest.ConfigurationRest;
-import de.micromata.borgbutler.server.user.UserFilter;
-import org.apache.commons.lang3.ArrayUtils;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.*;
-import org.eclipse.jetty.servlets.CrossOriginFilter;
-import org.eclipse.jetty.util.resource.Resource;
-import org.glassfish.jersey.jackson.JacksonFeature;
-import org.glassfish.jersey.media.multipart.MultiPartFeature;
-import org.glassfish.jersey.server.ResourceConfig;
-import org.glassfish.jersey.servlet.ServletContainer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.DispatcherType;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.EnumSet;
-
-public class JettyServer {
- private static Logger log = LoggerFactory.getLogger(JettyServer.class);
- private static String BIND_ADDRESS = "127.0.0.1";
- private static final int MAX_PORT_NUMBER = 65535;
- private Server server;
- private int port;
-
- static {
- String bindAddress = System.getProperty("bindAddress");
- if (bindAddress != null) {
- BIND_ADDRESS = bindAddress;
- }
- log.info("Binding server to address: " + BIND_ADDRESS);
- }
-
- public static String getBindAddress() {
- return BIND_ADDRESS;
- }
-
- public void start(String... restPackageNames) {
- port = findFreePort();
- if (port == -1) {
- return;
- }
- log.info("Starting web server on port " + port);
- server = new Server();
-
- ServerConnector connector = new ServerConnector(server);
- connector.setHost(BIND_ADDRESS);
- connector.setPort(port);
- server.setConnectors(new Connector[]{connector});
-
- ServletContextHandler ctx =
- new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
- ctx.setContextPath("/");
-
- ResourceConfig resourceConfig = new ResourceConfig();
- String[] packageNames = {ConfigurationRest.class.getPackage().getName()};
- if (restPackageNames != null && restPackageNames.length > 0) {
- packageNames = (String[]) ArrayUtils.addAll(packageNames, restPackageNames);
- }
- resourceConfig.packages(packageNames);
- resourceConfig.register(MultiPartFeature.class)
- .register(JacksonFeature.class);
- // .register(LoggingFilter.class)
- // .property("jersey.config.server.tracing.type", "ALL")
- // .property("jersey.config.server.tracing.threshold", "VERBOSE"))
- ServletHolder jerseyServlet = new ServletHolder(
- new ServletContainer(resourceConfig));
- jerseyServlet.setInitOrder(1);
- ctx.addServlet(jerseyServlet, "/rest/*");
- ctx.addFilter(UserFilter.class, "/rest/*", EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
- // Following code doesn't work:
- // jerseyServlet.setInitParameter("useFileMappedBuffer", "false");
- // jerseyServlet.setInitParameter("cacheControl","max-age=0,public");
-
- try {
- Path path;
- if (RunningMode.isDevelopmentMode()) {
- path = Paths.get(ServerConfiguration.getApplicationHome(), "borgbutler-webapp", "build");
- } else {
- path = Paths.get(ServerConfiguration.getApplicationHome(), "web");
- }
- if (!Files.exists(path)) {
- log.error("********** Fatal: Can't find web archive: " + path.toAbsolutePath());
- }
- URL url = path.toUri().toURL();
- log.debug("Using web directory: " + url);
- ctx.setBaseResource(Resource.newResource(url));
- } catch (IOException ex) {
- log.error(ex.getMessage(), ex);
- return;
- }
- ctx.setWelcomeFiles(new String[]{"index.html"});
- ctx.setInitParameter(DefaultServlet.CONTEXT_INIT + "cacheControl", "no-store,no-cache,must-revalidate");//"max-age=5,public");
- ctx.setInitParameter(DefaultServlet.CONTEXT_INIT + "useFileMappedBuffer", "false");
- ctx.addServlet(DefaultServlet.class, "/");
-
- ErrorPageErrorHandler errorHandler = new ErrorPageErrorHandler();
- errorHandler.addErrorPage(404, "/");
- ctx.setErrorHandler(errorHandler);
-
- if (RunningMode.isDevelopmentMode() || ServerConfiguration.get().isWebDevelopmentMode()) {
- log.warn("*********************************");
- log.warn("*********** **********");
- log.warn("*********** ATTENTION! **********");
- log.warn("*********** **********");
- log.warn("*********** Running in **********");
- log.warn("*********** dev mode! **********");
- log.warn("*********** **********");
- log.warn("*********************************");
- log.warn("Don't deliver this app in dev mode due to security reasons (CrossOriginFilter is set)!");
-
- FilterHolder filterHolder = ctx.addFilter(CrossOriginFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
- filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
- filterHolder.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
- filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,POST,HEAD");
- filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_HEADERS_PARAM, "X-Requested-With,Content-Type,Accept,Origin");
- }
-
- server.setHandler(ctx);
-
- try {
- server.start();
- } catch (Exception ex) {
- log.error("Can't start jetty: " + ex.getMessage(), ex);
- }
- }
-
- public void stop() {
- log.info("Stopping web server.");
- try {
- server.stop();
- } catch (Exception ex) {
- log.error("Can't stop web server: " + ex.getMessage(), ex);
- }
- if (server != null) {
- server.destroy();
- }
- }
-
- private int findFreePort() {
- int port = ServerConfiguration.get().getPort();
- return findFreePort(port);
- }
-
- private int findFreePort(int startPort) {
- int port = startPort > 0 ? startPort : 1;
- if (port > MAX_PORT_NUMBER) {
- log.warn("Port can't be higher than " + MAX_PORT_NUMBER + ": " + port + ". It's a possible mis-configuration.");
- port = ServerConfiguration.WEBSERVER_PORT_DEFAULT;
- }
- for (int i = port; i < port + 10; i++) {
- try (ServerSocket socket = new ServerSocket()) {
- socket.bind(new InetSocketAddress(BIND_ADDRESS, i));
- return i;
- } catch (Exception ex) {
- log.info("Port " + i + " already in use or not available. Trying next port.");
- continue; // try next port
- }
- }
- if (startPort != ServerConfiguration.WEBSERVER_PORT_DEFAULT) {
- log.info("Trying to fix port due to a possible mis-configuration.");
- return findFreePort(ServerConfiguration.WEBSERVER_PORT_DEFAULT);
- }
- log.error("No free port found! Giving up.");
- return -1;
- }
-
- public int getPort() {
- return port;
- }
-
- public String getUrl() {
- return "http://" + BIND_ADDRESS + ":" + port + "/";
- }
-}
-
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/Log4jMemoryAppender.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/Log4jMemoryAppender.java
deleted file mode 100644
index 5218556..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/Log4jMemoryAppender.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package de.micromata.borgbutler.server.logging;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.AppenderSkeleton;
-import org.apache.log4j.spi.LoggingEvent;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-
-public class Log4jMemoryAppender extends AppenderSkeleton {
- private static final int MAX_RESULT_SIZE = 1000;
- private static final int QUEUE_SIZE = 10000;
- private static Log4jMemoryAppender instance;
-
- private int lastLogEntryOrderNumber = -1;
-
- public static Log4jMemoryAppender getInstance() {
- return instance;
- }
-
- public Log4jMemoryAppender() {
- if (instance != null) {
- throw new IllegalArgumentException("Log4jMemoryAppender shouldn't be instantiated twice!");
- }
- instance = this;
- }
-
- /**
- * For test purposes.
- */
- Log4jMemoryAppender(boolean ignoreMultipleInstance) {
-
- }
-
- FiFoBuffer<LoggingEventData> queue = new FiFoBuffer<>(QUEUE_SIZE);
-
- @Override
- protected void append(LoggingEvent event) {
- LoggingEventData eventData = new LoggingEventData(event);
- eventData.orderNumber = ++lastLogEntryOrderNumber;
- queue.add(eventData);
- }
-
- /**
- * For testing purposes.
- *
- * @param event
- */
- void append(LoggingEventData event) {
- queue.add(event);
- }
-
- public List<LoggingEventData> query(LogFilter filter, Locale locale) {
- List<LoggingEventData> result = new ArrayList<>();
- if (filter == null) {
- return result;
- }
- int maxSize = filter.getMaxSize() != null ? filter.getMaxSize() : MAX_RESULT_SIZE;
- if (maxSize > MAX_RESULT_SIZE) {
- maxSize = MAX_RESULT_SIZE;
- }
- int counter = 0;
- //I18n i18n = CoreI18n.getDefault().get(locale);
- if (filter.isAscendingOrder()) {
- for (int i = 0; i < queue.getSize(); i++) {
- LoggingEventData resultEvent = getResultEvent(filter, queue.get(i), locale);
- if (resultEvent == null) continue;
- result.add(resultEvent);
- if (++counter > maxSize) break;
- }
- } else {
- for (int i = queue.getSize(); i >= 0; i--) {
- LoggingEventData resultEvent = getResultEvent(filter, queue.get(i), locale);
- if (resultEvent == null) continue;
- result.add(resultEvent);
- if (++counter > maxSize) break;
- }
- }
- return result;
- }
-
- private LoggingEventData getResultEvent(LogFilter filter, LoggingEventData event, Locale locale) {
- if (event == null) {
- return null;
- }
- if (!event.getLevel().matches(filter.getThreshold())) {
- return null;
- }
- if (filter.getLastReceivedLogOrderNumber() != null) {
- if (event.getOrderNumber() <= filter.getLastReceivedLogOrderNumber()) {
- return null;
- }
- }
- String logString = null;
- String message = event.getMessage();
- boolean localizedMessage = false;
- /*if (message != null && message.startsWith("i18n=")) {
- I18nLogEntry i18nLogEntry = I18nLogEntry.parse(message);
- message = i18n.formatMessage(i18nLogEntry.getI18nKey(), (Object[])i18nLogEntry.getArgs());
- localizedMessage = true;
- }*/
-
- if (StringUtils.isNotBlank(filter.getSearch())) {
- StringBuilder sb = new StringBuilder();
- sb.append(event.getLogDate());
- append(sb, event.getLevel(), true);
- append(sb, message, true);
- append(sb, event.getJavaClass(), true);
- append(sb, event.getStackTrace(), filter.isShowStackTraces());
- logString = sb.toString();
- }
- if (logString == null || matches(logString, filter.getSearch())) {
- LoggingEventData resultEvent = event;
- if (localizedMessage) {
- // Need a clone
- resultEvent = event.clone();
- resultEvent.setMessage(message);
- }
- return resultEvent;
- }
- return null;
- }
-
- private void append(StringBuilder sb, Object value, boolean append) {
- if (!append || value == null) {
- return;
- }
- sb.append("|#|").append(value);
- }
-
- public void close() {
- }
-
- public boolean requiresLayout() {
- return false;
- }
-
- private boolean matches(String str, String searchString) {
- if (StringUtils.isBlank(str)) {
- return StringUtils.isBlank(searchString);
- }
- if (StringUtils.isBlank(searchString)) {
- return true;
- }
- return str.toLowerCase().contains(searchString.toLowerCase());
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LogLevel.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LogLevel.java
index a4f8b69..41ecebb 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LogLevel.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LogLevel.java
@@ -1,8 +1,8 @@
package de.micromata.borgbutler.server.logging;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Level;
-import org.apache.log4j.spi.LoggingEvent;
public enum LogLevel {
ERROR, WARN, INFO, DEBUG, TRACE;
@@ -18,10 +18,8 @@
return this.ordinal() <= treshold.ordinal();
}
- public static LogLevel getLevel(LoggingEvent event) {
+ public static LogLevel getLevel(ILoggingEvent event) {
switch (event.getLevel().toInt()) {
- case Level.ERROR_INT:
- return LogLevel.ERROR;
case Level.INFO_INT:
return LogLevel.INFO;
case Level.DEBUG_INT:
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LoggingEventData.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LoggingEventData.java
index 2c39631..f00c60c 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LoggingEventData.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/logging/LoggingEventData.java
@@ -1,8 +1,10 @@
package de.micromata.borgbutler.server.logging;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.ThrowableProxyUtil;
+import ch.qos.logback.core.CoreConstants;
import org.apache.commons.lang3.ClassUtils;
-import org.apache.log4j.spi.LocationInfo;
-import org.apache.log4j.spi.LoggingEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -23,26 +25,26 @@
private String logDate;
String javaClass;
private String javaClassSimpleName;
- private String lineNumber;
+ private int lineNumber;
private String methodName;
private String stackTrace;
LoggingEventData() {
-
}
- public LoggingEventData(LoggingEvent event) {
+ public LoggingEventData(ILoggingEvent event) {
level = LogLevel.getLevel(event);
- message = event.getRenderedMessage();
+ message = event.getFormattedMessage();
messageObjectClass = event.getMessage().getClass().toString();
loggerName = event.getLoggerName();
- logDate = getIsoLogDate(event.timeStamp);
- LocationInfo info = event.getLocationInformation();
- Throwable throwable = event.getThrowableInformation() != null ? event.getThrowableInformation().getThrowable() : null;
- if (throwable != null) {
+ logDate = getIsoLogDate(event.getTimeStamp());
+ StackTraceElement info = event.getCallerData()[0];
+ IThrowableProxy throwableProxy = event.getThrowableProxy();
+ if (throwableProxy != null) {
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
- throwable.printStackTrace(printWriter);
+ printWriter.append(ThrowableProxyUtil.asString(throwableProxy));
+ printWriter.append(CoreConstants.LINE_SEPARATOR);
stackTrace = writer.toString();
}
if (info != null) {
@@ -81,7 +83,7 @@
return javaClassSimpleName;
}
- public String getLineNumber() {
+ public int getLineNumber() {
return lineNumber;
}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
deleted file mode 100644
index 90fe225..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.BorgCommands;
-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;
-import de.micromata.borgbutler.json.borg.BorgFilesystemItem;
-import de.micromata.borgbutler.utils.DirUtils;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.math.NumberUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.awt.*;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.List;
-
-@Path("/archives")
-public class ArchivesRest {
- private static Logger log = LoggerFactory.getLogger(ArchivesRest.class);
-
- /**
- * @param repoName Name of repository ({@link Repository#getName()}.
- * @param archiveId 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)
- */
- @GET
- @Produces(MediaType.APPLICATION_JSON)
- public String getArchive(@QueryParam("repo") String repoName,
- @QueryParam("archiveId") String archiveId, @QueryParam("force") boolean force,
- @QueryParam("prettyPrinter") boolean prettyPrinter) {
- Archive archive = ButlerCache.getInstance().getArchive(repoName, archiveId, force);
- if (force) {
- ButlerCache.getInstance().deleteCachedArchiveContent(repoName, archiveId);
- }
- return JsonUtils.toJson(archive, prettyPrinter);
- }
-
- /**
- * @param archiveId Id or name of archive.
- * @param searchString The string to search for (key words separated by white chars, trailing ! char represents exclude).
- * @param mode Flat (default) or tree.
- * @param currentDirectory The current displayed directory (only files and directories contained will be returned).
- * @param maxResultSize maximum number of file items to return (default is 50).
- * @param diffArchiveId If given, the differences between archiveId and diffArchiveId will be returned.
- * @param autoChangeDirectoryToLeafItem If given, this method will step automatically into single sub directories.
- * @param force If false (default), non cached file lists will not be loaded by borg.
- * @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)
- */
- @GET
- @Path("filelist")
- @Produces(MediaType.APPLICATION_JSON)
- public String getArchiveFileList(@QueryParam("archiveId") String archiveId,
- @QueryParam("searchString") String searchString,
- @QueryParam("mode") String mode,
- @QueryParam("currentDirectory") String currentDirectory,
- @QueryParam("maxResultSize") String maxResultSize,
- @QueryParam("diffArchiveId") String diffArchiveId,
- @QueryParam("autoChangeDirectoryToLeafItem") boolean autoChangeDirectoryToLeafItem,
- @QueryParam("force") boolean force,
- @QueryParam("prettyPrinter") boolean prettyPrinter) {
- boolean diffMode = StringUtils.isNotBlank(diffArchiveId);
- int maxSize = NumberUtils.toInt(maxResultSize, 50);
- FileSystemFilter filter = diffMode ? new DiffFileSystemFilter() : new FileSystemFilter();
- filter.setSearchString(searchString)
- .setCurrentDirectory(currentDirectory)
- .setAutoChangeDirectoryToLeafItem(autoChangeDirectoryToLeafItem);
- List<BorgFilesystemItem> items = null;
- 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\"}]";
- }
- }
- return JsonUtils.toJson(items, prettyPrinter);
- }
-
- /**
- * @param archiveId
- * @param openDownloads
- * @param fileNumber The fileNumber of the file or directory in the archive served by BorgButler's
- */
- @GET
- @Path("/restore")
- @Produces(MediaType.APPLICATION_OCTET_STREAM)
- public Response restore(@QueryParam("archiveId") String archiveId,
- @QueryParam("openDownloads") boolean openDownloads,
- @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,
- filter);
- if (CollectionUtils.isEmpty(items)) {
- log.error("Requested file #" + fileNumber + " not found in archive '" + archiveId
- + ". (May-be the archive content isn't yet loaded to the cache.");
- Response.ResponseBuilder builder = Response.status(Response.Status.NOT_FOUND);
- return builder.build();
- }
- if (items.size() != 1) {
- log.error("Requested file #" + fileNumber + " found multiple times (" + items.size() + ") in archive '" + archiveId
- + "! Please remove the archive files (may-be corrupted).");
- Response.ResponseBuilder builder = Response.status(404);
- return builder.build();
- }
- Archive archive = ButlerCache.getInstance().getArchive(archiveId);
- if (archive == null) {
- Response.ResponseBuilder builder = Response.status(Response.Status.NOT_FOUND);
- return builder.build();
- }
- BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(archive.getRepoId());
- try {
- BorgFilesystemItem 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());
- if (CollectionUtils.isEmpty(files)) {
- log.error("No files extracted.");
- Response.ResponseBuilder builder = Response.status(Response.Status.NOT_FOUND);
- return builder.build();
- }
- if (openDownloads)
- openFileBrowser(new File(restoreDir, item.getPath()));
- Response.ResponseBuilder builder = Response.status(Response.Status.ACCEPTED);
- return builder.build();
- } catch (IOException ex) {
- log.error("No file extracted: " + ex.getMessage(), ex);
- Response.ResponseBuilder builder = Response.status(Response.Status.NOT_FOUND);
- return builder.build();
- }
- }
-
- private void openFileBrowser(File fileDirectory) {
- if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
- File file = fileDirectory;
- if (!fileDirectory.exists() || Files.isSymbolicLink(fileDirectory.toPath())) {
- // Open parent.
- file = fileDirectory.getParentFile();
- }
- Desktop.getDesktop().browseFileDirectory(file);
- }
- }
-
- private Response handleRestoredFiles(BorgRepoConfig repoConfig, Archive archive) {
- // Todo: Handle download of single files as well as download of zip archive (if BorgButler runs remote).
- return null;
- /* File file = path.toFile();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- FileUtils.copyFile(file, baos);
- } catch (IOException ex) {
- log.error(ex.getMessage(), ex);
- }
- BorgFilesystemItem item = items.get(0);
- file = new File(item.getPath());
- byte[] byteArray = baos.toByteArray();//result.getAsByteArrayOutputStream().toByteArray();
- Response.ResponseBuilder builder = Response.ok(byteArray);
- builder.header("Content-Disposition", "attachment; filename=" + file.getName());
- // Needed to get the Content-Disposition by client:
- builder.header("Access-Control-Expose-Headers", "Content-Disposition");
- Response response = builder.build();
- return response;
-
- try {
- //java.nio.file.Path tempDirWithPrefix = Files.createTempDirectory("borgbutler-extract-");
- File restoreHomeDir = ConfigurationHandler.getConfiguration().getRestoreHomeDir();
- File restoreDir = BorgCommands.extractFiles(restoreHomeDir, repoConfig, archive.getName(), item.getPath());
- openFileBrowser(restoreDir);
- List<java.nio.file.Path> files = DirUtils.listFiles(tempDir);
- if (CollectionUtils.isEmpty(files)) {
- log.error("No file extracted.");
- Response.ResponseBuilder builder = Response.status(404);
- return builder.build();
- }
- path = files.get(0);
- } catch (IOException ex) {
- log.error("No file extracted: " + ex.getMessage(), ex);
- Response.ResponseBuilder builder = Response.status(404);
- return builder.build();
- } finally {
- if (tempDir != null) {
- try {
- FileUtils.deleteDirectory(tempDir.toFile());
- } catch (IOException ex) {
- log.error("Error while trying to delete temporary directory '" + tempDir.toString() + "': " + ex.getMessage(), ex);
- }
- }
- }*/
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java
deleted file mode 100644
index 0ae66d6..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.BorgCommandResult;
-import de.micromata.borgbutler.BorgCommands;
-import de.micromata.borgbutler.cache.ButlerCache;
-import de.micromata.borgbutler.config.BorgRepoConfig;
-import de.micromata.borgbutler.config.ConfigurationHandler;
-import de.micromata.borgbutler.data.Repository;
-import de.micromata.borgbutler.jobs.JobResult;
-import de.micromata.borgbutler.json.JsonUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.*;
-import javax.ws.rs.core.MediaType;
-
-@Path("/repoConfig")
-public class BorgRepoConfigsRest {
- private static Logger log = LoggerFactory.getLogger(BorgRepoConfigsRest.class);
-
- /**
- * @param id id or name of repo.
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @return {@link BorgRepoConfig} as json string.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Produces(MediaType.APPLICATION_JSON)
- public String getRepoConfig(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) {
- BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(id);
- return JsonUtils.toJson(repoConfig, prettyPrinter);
- }
-
- @POST
- @Produces(MediaType.TEXT_PLAIN)
- public void setRepoConfig(String jsonConfig) {
- BorgRepoConfig newRepoConfig = JsonUtils.fromJson(BorgRepoConfig.class, jsonConfig);
- if (newRepoConfig == null) {
- log.error("Internal Rest error. Can't parse BorgRepoConfig: " + jsonConfig);
- return;
- }
- if ("new".equals(newRepoConfig.getId())) {
- newRepoConfig.setId(null);
- ConfigurationHandler.getConfiguration().add(newRepoConfig);
- } else if ("init".equals(newRepoConfig.getId())) {
- newRepoConfig.setId(null);
- ConfigurationHandler.getConfiguration().add(newRepoConfig);
- } else {
- BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(newRepoConfig.getId());
- if (repoConfig == null) {
- log.error("Can't find repo config '" + newRepoConfig.getId() + "'. Can't save new settings.");
- return;
- }
- ButlerCache.getInstance().clearRepoCacheAccess(repoConfig.getRepo());
- ButlerCache.getInstance().clearRepoCacheAccess(newRepoConfig.getRepo());
- repoConfig.copyFrom(newRepoConfig);
- }
- ConfigurationHandler.getInstance().save();
- }
-
- /**
- * @param idOrName id or name of repo to remove from BorgButler.
- * @return "OK" if removed or error string.
- */
- @GET
- @Path("remove")
- @Produces(MediaType.APPLICATION_JSON)
- public String removeRepoConfig(@QueryParam("id") String idOrName) {
- boolean result = ConfigurationHandler.getConfiguration().remove(idOrName);
- if (!result) {
- String error = "Repo config with id or name '" + idOrName + "' not found. Can't remove the repo.";
- log.error(error);
- return error;
- }
- ConfigurationHandler.getInstance().save();
- return "OK";
- }
-
- /**
- * @param jsonRepoConfig All configuration value of the repo to check.
- * @return Result of borg (tbd.).
- */
- @POST
- @Path("check")
- @Produces(MediaType.APPLICATION_JSON)
- public String checkConfig(String jsonRepoConfig) {
- log.info("Testing repo config: " + jsonRepoConfig);
- BorgRepoConfig repoConfig = JsonUtils.fromJson(BorgRepoConfig.class, jsonRepoConfig);
- BorgCommandResult<Repository> result = BorgCommands.info(repoConfig);
- return result.getStatus() == JobResult.Status.OK ? "OK" : result.getError();
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationInfo.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationInfo.java
deleted file mode 100644
index 7f41d55..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationInfo.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.server.BorgVersion;
-import de.micromata.borgbutler.server.ServerConfiguration;
-
-public class ConfigurationInfo {
- private ServerConfiguration serverConfiguration;
- private BorgVersion borgVersion;
-
- public ServerConfiguration getServerConfiguration() {
- return this.serverConfiguration;
- }
-
- public BorgVersion getBorgVersion() {
- return this.borgVersion;
- }
-
- public ConfigurationInfo setServerConfiguration(ServerConfiguration serverConfiguration) {
- this.serverConfiguration = serverConfiguration;
- return this;
- }
-
- public ConfigurationInfo setBorgVersion(BorgVersion borgVersion) {
- this.borgVersion = borgVersion;
- return this;
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java
deleted file mode 100644
index a5b2ad4..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.cache.ButlerCache;
-import de.micromata.borgbutler.config.ConfigurationHandler;
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.server.BorgInstallation;
-import de.micromata.borgbutler.server.ServerConfiguration;
-import de.micromata.borgbutler.server.user.UserData;
-import de.micromata.borgbutler.server.user.UserManager;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.*;
-import javax.ws.rs.core.MediaType;
-
-@Path("/configuration")
-public class ConfigurationRest {
- private Logger log = LoggerFactory.getLogger(ConfigurationRest.class);
-
- /**
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Path("config")
- @Produces(MediaType.APPLICATION_JSON)
- public String getConfig(@QueryParam("prettyPrinter") boolean prettyPrinter) {
- ConfigurationInfo configurationInfo = new ConfigurationInfo();
- configurationInfo.setServerConfiguration(ServerConfiguration.get());
- configurationInfo.setBorgVersion(BorgInstallation.getInstance().getBorgVersion());
- String json = JsonUtils.toJson(configurationInfo, prettyPrinter);
- return json;
- }
-
- @POST
- @Path("config")
- @Produces(MediaType.TEXT_PLAIN)
- public void setConfig(String jsonConfig) {
- ConfigurationHandler configurationHandler = ConfigurationHandler.getInstance();
- ConfigurationInfo configurationInfo = JsonUtils.fromJson(ConfigurationInfo.class, jsonConfig);
- BorgInstallation.getInstance().configure(configurationInfo.getServerConfiguration(), configurationInfo.getBorgVersion().getBorgBinary());
- ServerConfiguration configuration = ServerConfiguration.get();
- configuration.copyFrom(configurationInfo.getServerConfiguration());
- configurationHandler.save();
- }
-
- /**
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Path("user")
- @Produces(MediaType.APPLICATION_JSON)
- public String getUser(@QueryParam("prettyPrinter") boolean prettyPrinter) {
- UserData user = RestUtils.getUser();
- String json = JsonUtils.toJson(user, prettyPrinter);
- return json;
- }
-
- @POST
- @Path("user")
- @Produces(MediaType.TEXT_PLAIN)
- public void setUser(String jsonConfig) {
- UserData user = JsonUtils.fromJson(UserData.class, jsonConfig);
- if (user.getLocale() != null && StringUtils.isBlank(user.getLocale().getLanguage())) {
- // Don't set locale with "" as language.
- user.setLocale(null);
- }
- if (StringUtils.isBlank(user.getDateFormat())) {
- // Don't set dateFormat as "".
- user.setDateFormat(null);
- }
- UserManager.instance().saveUser(user);
- }
-
- /**
- * Resets the settings to default values (deletes all settings).
- */
- @GET
- @Path("clearAllCaches")
- @Produces(MediaType.APPLICATION_JSON)
- public String clearAllCaches() {
- log.info("Clear all caches called...");
- ButlerCache.getInstance().clearAllCaches();
- return "OK";
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.java
deleted file mode 100644
index 5045ef2..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.server.RunningMode;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.swing.*;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import java.awt.*;
-import java.io.File;
-
-@Path("/files")
-public class FilesystemBrowserRest {
- private Logger log = LoggerFactory.getLogger(FilesystemBrowserRest.class);
-
- /**
- * Opens a directory browser or file browser on the desktop app and returns the chosen dir/file. Works only if Browser and Desktop app are running
- * on the same host.
- *
- * @param current The current path of file. If not given the directory/file browser starts with the last used directory or user.home.
- * @return The chosen directory path (absolute path).
- */
- @GET
- @Path("/browse-local-filesystem")
- @Produces(MediaType.APPLICATION_JSON)
- public String browseLocalFilesystem(@Context HttpServletRequest requestContext, @QueryParam("current") String current) {
- String msg = RestUtils.checkLocalDesktopAvailable(requestContext);
- if (msg != null) {
- log.info(msg);
- return msg;
- }
- if (fileDialog != null || fileChooser != null) {
- log.warn("Cannot call already opened file choose twice. Close file chooser first.");
- return "{\"directory\": \"\"}";
- }
- File file = null;
- synchronized (FilesystemBrowserRest.class) {
- if (frame == null) {
- frame = new JFrame("BorgButler");
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setSize(300, 100);
- frame.setResizable(false);
- frame.setLocationRelativeTo(null);
- frame.setBackground(Color.WHITE);
- frame.getContentPane().setBackground(Color.WHITE);
- JLabel label = new JLabel("Click for choosing directory...", SwingConstants.CENTER);
- frame.add(label);
- }
- if (RunningMode.getOSType() == RunningMode.OSType.MAC_OS) {
- // The JFileChooser will hang after several calls, use AWT file dialog instead for Mac OS:
- System.setProperty("apple.awt.fileDialogForDirectories", "true");
- frame.setAlwaysOnTop(true);
- frame.setVisible(true);
- try {
- fileDialog = new FileDialog(frame, "Choose a directory", FileDialog.LOAD);
- if (StringUtils.isNotBlank(current)) {
- fileDialog.setDirectory(current);
- }
- fileDialog.toFront();
- fileDialog.setVisible(true);
- String filename = fileDialog.getFile();
- String directory = fileDialog.getDirectory();
- frame.setVisible(false);
- if (filename == null) {
- return "";
- }
- file = new File(directory, filename);
- if (!file.isDirectory()) {
- file = new File(directory);
- }
- } finally {
- fileDialog = null;
- }
- } else {
- try {
- if (StringUtils.isNotBlank(current)) {
- fileChooser = new JFileChooser(current);
- } else {
- fileChooser = new JFileChooser();
- }
- fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
- frame.setVisible(true);
- frame.setAlwaysOnTop(true);
- int returnCode = fileChooser.showDialog(frame, "Choose");
- frame.setVisible(false);
- frame.setAlwaysOnTop(false);
- if (returnCode == JFileChooser.APPROVE_OPTION) {
- file = fileChooser.getSelectedFile();
- }
- } finally {
- fileChooser = null;
- }
- }
- }
- String filename = file != null ? JsonUtils.toJson(file.getAbsolutePath()) : "";
- String result = "{\"directory\":\"" + filename + "\"}";
- return result;
- }
-
- /**
- * @return OK, if the local desktop services such as open file browser etc. are available.
- */
- @GET
- @Path("/local-fileservices-available")
- @Produces(MediaType.TEXT_PLAIN)
- public String browseLocalFilesystem(@Context HttpServletRequest requestContext) {
- String msg = RestUtils.checkLocalDesktopAvailable(requestContext);
- if (msg != null) {
- log.info(msg);
- return msg;
- }
- return "OK";
- }
-
- private static JFrame frame;
- private static FileDialog fileDialog;
- private static JFileChooser fileChooser;
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java
deleted file mode 100644
index b17c855..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/I18nRest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.server.I18nClientMessages;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import java.util.Locale;
-import java.util.Map;
-
-@Path("/i18n")
-public class I18nRest {
- private Logger log = LoggerFactory.getLogger(I18nRest.class);
-
- /**
- *
- * @param requestContext For detecting the user's client locale.
- * @param locale If not given, the client's language (browser) will be used.
- * @param keysOnly If true, only the keys will be returned. Default is false.
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Path("list")
- @Produces(MediaType.APPLICATION_JSON)
- public String getList(@Context HttpServletRequest requestContext, @QueryParam("prettyPrinter") boolean prettyPrinter,
- @QueryParam("keysOnly") boolean keysOnly, @QueryParam("locale") String locale) {
- Locale localeObject;
- if (StringUtils.isNotBlank(locale)) {
- localeObject = new Locale(locale);
- } else {
- localeObject = RestUtils.getUserLocale(requestContext);
- }
- Map<String, String> translations = I18nClientMessages.getInstance().getAllMessages(localeObject, keysOnly);
- String json = JsonUtils.toJson(translations, prettyPrinter);
- return json;
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java
deleted file mode 100644
index be744f0..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.BorgJob;
-import de.micromata.borgbutler.BorgQueueExecutor;
-import de.micromata.borgbutler.config.BorgRepoConfig;
-import de.micromata.borgbutler.config.ConfigurationHandler;
-import de.micromata.borgbutler.jobs.AbstractJob;
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.json.borg.ProgressInfo;
-import de.micromata.borgbutler.server.rest.queue.JsonJob;
-import de.micromata.borgbutler.server.rest.queue.JsonJobQueue;
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import java.util.ArrayList;
-import java.util.List;
-
-@Path("/jobs")
-public class JobsRest {
- private static Logger log = LoggerFactory.getLogger(JobsRest.class);
-
- private static List<JsonJobQueue> testList, oldJobsTestList;
-
- /**
- * @param repo If given, only the job queue of the given repo will be returned.
- * @param testMode If true, then a test job list is created.
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @return Job queues as json string.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Produces(MediaType.APPLICATION_JSON)
- public String getJobs(@QueryParam("repo") String repo,
- @QueryParam("testMode") boolean testMode,
- @QueryParam("oldJobs") boolean oldJobs,
- @QueryParam("prettyPrinter") boolean prettyPrinter) {
- log.debug("getJobs repo=" + repo + ", oldJobs=" + oldJobs);
- if (testMode) {
- // Return dynamic test queue:
- return returnTestList(oldJobs, prettyPrinter);
- }
- boolean validRepo = false;
- if (StringUtils.isNotBlank(repo) && !"null".equals(repo) && !"undefined".equals(repo)) {
- validRepo = true;
- }
- BorgQueueExecutor borgQueueExecutor = BorgQueueExecutor.getInstance();
- List<JsonJobQueue> queueList = new ArrayList<>();
- if (validRepo) { // Get only the queue of the given repo:
- JsonJobQueue queue = getQueue(repo, oldJobs);
- if (queue != null) {
- queueList.add(queue);
- }
- } else { // Get all the queues (of all repos).
- for (String rep : borgQueueExecutor.getRepos()) {
- JsonJobQueue queue = getQueue(rep, oldJobs);
- if (queue != null) {
- queueList.add(queue);
- }
- }
- }
- return JsonUtils.toJson(queueList, prettyPrinter);
- }
-
- private JsonJobQueue getQueue(String repo, boolean oldJobs) {
- BorgQueueExecutor borgQueueExecutor = BorgQueueExecutor.getInstance();
- BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(repo);
- if (repoConfig == null) {
- return null;
- }
- List<BorgJob<?>> borgJobList = borgQueueExecutor.getJobListCopy(repoConfig, oldJobs);
- if (CollectionUtils.isEmpty(borgJobList))
- return null;
- JsonJobQueue queue = new JsonJobQueue().setRepo(repoConfig.getDisplayName());
- queue.setJobs(new ArrayList<>(borgJobList.size()));
- for (BorgJob<?> borgJob : borgJobList) {
- JsonJob job = new JsonJob(borgJob);
- queue.getJobs().add(job);
- }
- return queue;
- }
-
- /**
- * @param uniqueJobNumberString The id of the job to cancel.
- */
- @Path("/cancel")
- @GET
- public void cancelJob(@QueryParam("uniqueJobNumber") String uniqueJobNumberString) {
- Long uniqueJobNumber = null;
- try {
- uniqueJobNumber = Long.parseLong(uniqueJobNumberString);
- } catch (NumberFormatException ex) {
- log.error("Can't cancel job, because unique job number couln't be parsed (long value expected): " + uniqueJobNumberString);
- return;
- }
- BorgQueueExecutor.getInstance().cancelJob(uniqueJobNumber);
- }
-
- /**
- * Only for test purposes and development.
- *
- * @param oldJobs
- * @param prettyPrinter
- * @return
- */
- private String returnTestList(boolean oldJobs, boolean prettyPrinter) {
- List<JsonJobQueue> list = oldJobs ? oldJobsTestList : testList;
- if (list == null) {
- list = new ArrayList<>();
- long uniqueJobNumber = 100000;
- JsonJobQueue queue = new JsonJobQueue().setRepo("My Computer");
- addTestJob(queue, "info", "my-macbook", 0, 2342, uniqueJobNumber++, oldJobs);
- addTestJob(queue, "list", "my-macbook", -1, -1, uniqueJobNumber++, oldJobs);
- list.add(queue);
-
- queue = new JsonJobQueue().setRepo("My Server");
- addTestJob(queue, "list", "my-server", 0, 1135821, uniqueJobNumber++, oldJobs);
- addTestJob(queue, "info", "my-server", -1, -1, uniqueJobNumber++, oldJobs);
- list.add(queue);
- if (oldJobs) {
- oldJobsTestList = list;
- } else {
- testList = list;
- }
- } else if (!oldJobs) {
- for (JsonJobQueue jobQueue : list) {
- for (JsonJob job : jobQueue.getJobs()) {
- if (job.getStatus() != AbstractJob.Status.RUNNING) continue;
- long current = job.getProgressInfo().getCurrent();
- long total = job.getProgressInfo().getTotal();
- if (StringUtils.startsWith(job.getProgressInfo().getMessage(), "Calculating")) {
- // Info is a faster operation:
- current += Math.random() * total / 5;
- } else {
- // than get the complete archive file list:
- current += Math.random() * total / 30;
- }
- if (current > total) {
- current = 0; // Reset to beginning.
- }
- job.getProgressInfo().setCurrent(current);
- if (job.getProgressText().startsWith("Calculating")) {
- job.getProgressInfo().setMessage("Calculating statistics... " + Math.round(100 * current / total) + "%");
- }
- job.buildProgressText();
- }
- }
- }
- return JsonUtils.toJson(list, prettyPrinter);
- }
-
- /**
- * Only for test purposes and development.
- *
- * @param queue
- * @param operation
- * @param host
- * @param current
- * @param total
- * @return
- */
- private JsonJob addTestJob(JsonJobQueue queue, String operation, String host, long current, long total, long uniqueNumber, boolean oldJobs) {
- ProgressInfo progressInfo = new ProgressInfo()
- .setCurrent(current)
- .setTotal(total);
- JsonJob job = new JsonJob()
- .setProgressInfo(progressInfo)
- .setStatus(AbstractJob.Status.QUEUED);
- if ("info".equals(operation)) {
- progressInfo.setMessage("Calculating statistics... ");
- job.setDescription("Loading info of archive '" + host + "-2018-12-05T23:10:33' of repo '" + queue.getRepo() + "'.")
- .setCommandLineAsString("borg info --json --log-json --progress ssh://...:23/./backups/" + host + "::" + host + "-2018-12-05T23:10:33");
- } else {
- progressInfo.setMessage("Getting file list... ");
- job.setDescription("Loading list of files of archive '" + host + "-2018-12-05T17:30:38' of repo '" + queue.getRepo() + "'.")
- .setCommandLineAsString("borg list --json-lines ssh://...:23/./backups/" + host + "::" + host + "-2018-12-05T17:30:38");
- }
- job.buildProgressText();
- if (current >= 0) {
- job.setStatus(AbstractJob.Status.RUNNING);
- } else {
- job.setStatus(AbstractJob.Status.QUEUED);
- }
- if (queue.getJobs() == null) {
- queue.setJobs(new ArrayList<>());
- }
- job.setUniqueJobNumber(uniqueNumber);
- if (oldJobs) {
- job.setStatus(uniqueNumber % 2 == 0 ? AbstractJob.Status.CANCELLED : AbstractJob.Status.DONE);
- }
- queue.getJobs().add(job);
- return job;
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/LoggingRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/LoggingRest.java
deleted file mode 100644
index e268a36..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/LoggingRest.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.server.logging.Log4jMemoryAppender;
-import de.micromata.borgbutler.server.logging.LogFilter;
-import de.micromata.borgbutler.server.logging.LogLevel;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-
-@Path("/logging")
-public class LoggingRest {
- private Logger log = LoggerFactory.getLogger(LoggingRest.class);
-
- /**
- * @param requestContext
- * @param search
- * @param logLevelTreshold fatal, error, warn, info, debug or trace (case insensitive).
- * @param maxSize Max size of the result list.
- * @param ascendingOrder Default is false (default is descending order).
- * @param lastReceivedOrderNumber The last received order number for updating log entries (preventing querying all entries again).
- * @param prettyPrinter
- * @return
- */
- @GET
- @Path("query")
- @Produces(MediaType.APPLICATION_JSON)
- public String query(@Context HttpServletRequest requestContext,
- @QueryParam("search") String search, @QueryParam("treshold") String logLevelTreshold,
- @QueryParam("maxSize") Integer maxSize, @QueryParam("ascendingOrder") Boolean ascendingOrder,
- @QueryParam("lastReceivedOrderNumber") Integer lastReceivedOrderNumber,
- @QueryParam("prettyPrinter") boolean prettyPrinter) {
- LogFilter filter = new LogFilter();
- filter.setSearch(search);
- if (logLevelTreshold != null) {
- try {
- LogLevel treshold = LogLevel.valueOf(logLevelTreshold.trim().toUpperCase());
- filter.setThreshold(treshold);
- } catch (IllegalArgumentException ex) {
- log.error("Can't parse log level treshold: " + logLevelTreshold + ". Supported values (case insensitive): " + LogLevel.getSupportedValues());
- }
- }
- if (filter.getThreshold() == null) {
- filter.setThreshold(LogLevel.INFO);
- }
- if (maxSize != null) {
- filter.setMaxSize(maxSize);
- }
- if (ascendingOrder != null && ascendingOrder == true) {
- filter.setAscendingOrder(true);
- }
- if (lastReceivedOrderNumber != null) {
- filter.setLastReceivedLogOrderNumber(lastReceivedOrderNumber);
- }
- Log4jMemoryAppender appender = Log4jMemoryAppender.getInstance();
- String json = JsonUtils.toJson(appender.query(filter, RestUtils.getUserLocale(requestContext)), prettyPrinter);
- return json;
- }
-}
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
deleted file mode 100644
index 337dfb6..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.cache.ButlerCache;
-import de.micromata.borgbutler.data.Repository;
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.json.borg.BorgRepository;
-import org.apache.commons.collections4.CollectionUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.MediaType;
-import java.util.List;
-
-@Path("/repos")
-public class ReposRest {
- private static Logger log = LoggerFactory.getLogger(ReposRest.class);
-
- /**
- *
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @return A list of repositories of type {@link BorgRepository}.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Path("list")
- @Produces(MediaType.APPLICATION_JSON)
- public String getList(@QueryParam("prettyPrinter") boolean prettyPrinter) {
- List<Repository> repositories = ButlerCache.getInstance().getAllRepositories();
- if (CollectionUtils.isEmpty(repositories)) {
- return "[]";
- }
- return JsonUtils.toJson(repositories, prettyPrinter);
- }
-
- /**
- *
- * @param id id or name of repo.
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @return {@link Repository} (without list of archives) as json string.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Path("repo")
- @Produces(MediaType.APPLICATION_JSON)
- public String getRepo(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) {
- Repository repository = ButlerCache.getInstance().getRepository(id);
- return JsonUtils.toJson(repository, prettyPrinter);
- }
-
- /**
- *
- * @param id id or name of repo.
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @return {@link Repository} (including list of archives) as json string.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Path("repoArchiveList")
- @Produces(MediaType.APPLICATION_JSON)
- public String getRepoArchiveList(@QueryParam("id") String id, @QueryParam("force") boolean force,
- @QueryParam("prettyPrinter") boolean prettyPrinter) {
- if (force) {
- Repository repo = ButlerCache.getInstance().getRepository(id);
- ButlerCache.getInstance().clearRepoCacheAccess(repo);
- }
- Repository repository = ButlerCache.getInstance().getRepositoryArchives(id);
- return JsonUtils.toJson(repository, prettyPrinter);
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/RestUtils.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/RestUtils.java
deleted file mode 100644
index a69231d..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/RestUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.server.RunningMode;
-import de.micromata.borgbutler.server.user.UserData;
-import de.micromata.borgbutler.server.user.UserUtils;
-import org.slf4j.Logger;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.core.Response;
-import java.util.Locale;
-
-public class RestUtils {
- /**
- * @return null, if the local app (JavaFX) is running and the request is from localhost. Otherwise message, why local
- * service isn't available.
- */
- public static String checkLocalDesktopAvailable(HttpServletRequest requestContext) {
- if (RunningMode.getServerType() != RunningMode.ServerType.DESKTOP) {
- return "Service unavailable. No desktop app on localhost available.";
- }
- String remoteAddr = requestContext.getRemoteAddr();
- if (remoteAddr == null || !remoteAddr.equals("127.0.0.1")) {
- return "Service not available. Can't call this service remote. Run this service on localhost of the running desktop app.";
- }
- return null;
- }
-
- /**
- * @return Returns the user put by the UserFilter.
- * @see UserUtils#getUser()
- * @see de.micromata.borgbutler.server.user.UserFilter
- */
- static UserData getUser() {
- UserData user = UserUtils.getUser();
- if (user == null) {
- throw new IllegalStateException("No user given in rest call.");
- }
- return UserUtils.getUser();
- }
-
- static Locale getUserLocale(HttpServletRequest requestContext) {
- UserData user = RestUtils.getUser();
- Locale locale = user.getLocale();
- if (locale == null) {
- locale = requestContext.getLocale();
- }
- return locale;
- }
-
- static Response get404Response(Logger log, String errorMessage) {
- log.error(errorMessage);
- Response response = Response.status(404).
- entity(errorMessage).
- type("text/plain").
- build();
- return response;
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfo.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfo.java
deleted file mode 100644
index 394730f..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfo.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.BorgQueueStatistics;
-import de.micromata.borgbutler.server.BorgVersion;
-
-/**
- * Statistics of all the job queues, especially the number of total queued and running jobs.
- * This is used e. g. by the client for showing a badge near to the menu entry "job monitor" with the number
- * of Jobs in the queues.
- */
-public class SystemInfo {
- private BorgQueueStatistics queueStatistics;
-
- private boolean configurationOK;
-
- private BorgVersion borgVersion;
-
- public BorgQueueStatistics getQueueStatistics() {
- return this.queueStatistics;
- }
-
- public boolean isConfigurationOK() {
- return this.configurationOK;
- }
-
- public BorgVersion getBorgVersion() {
- return this.borgVersion;
- }
-
- public SystemInfo setQueueStatistics(BorgQueueStatistics queueStatistics) {
- this.queueStatistics = queueStatistics;
- return this;
- }
-
- public SystemInfo setConfigurationOK(boolean configurationOK) {
- this.configurationOK = configurationOK;
- return this;
- }
-
- public SystemInfo setBorgVersion(BorgVersion borgVersion) {
- this.borgVersion = borgVersion;
- return this;
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfoRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfoRest.java
deleted file mode 100644
index 6f8112c..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/SystemInfoRest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.BorgQueueExecutor;
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.server.BorgInstallation;
-import de.micromata.borgbutler.server.BorgVersion;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.MediaType;
-
-@Path("/system")
-public class SystemInfoRest {
- private static Logger log = LoggerFactory.getLogger(SystemInfoRest.class);
-
- /**
- * @return The total number of jobs queued or running (and other statistics): {@link de.micromata.borgbutler.BorgQueueStatistics}.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Produces(MediaType.APPLICATION_JSON)
- @Path("info")
- public String getStatistics() {
- BorgVersion borgVersion = BorgInstallation.getInstance().getBorgVersion();
- SystemInfo systemInfonfo = new SystemInfo()
- .setQueueStatistics(BorgQueueExecutor.getInstance().getStatistics())
- .setConfigurationOK(borgVersion.isVersionOK())
- .setBorgVersion(borgVersion);
- return JsonUtils.toJson(systemInfonfo);
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java
deleted file mode 100644
index 82f033f..0000000
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/VersionRest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package de.micromata.borgbutler.server.rest;
-
-import de.micromata.borgbutler.json.JsonUtils;
-import de.micromata.borgbutler.server.Languages;
-import de.micromata.borgbutler.server.Version;
-import de.micromata.borgbutler.server.user.UserData;
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import java.util.Date;
-import java.util.Locale;
-
-@Path("/")
-public class VersionRest {
- private Logger log = LoggerFactory.getLogger(VersionRest.class);
-
- /**
- *
- * @param requestContext For detecting the user's client locale.
- * @param prettyPrinter If true then the json output will be in pretty format.
- * @see JsonUtils#toJson(Object, boolean)
- */
- @GET
- @Path("version")
- @Produces(MediaType.APPLICATION_JSON)
- public String getVersion(@Context HttpServletRequest requestContext, @QueryParam("prettyPrinter") boolean prettyPrinter) {
- UserData user = RestUtils.getUser();
- String language = Languages.asString(user.getLocale());
- if (StringUtils.isBlank(language)) {
- Locale locale = requestContext.getLocale();
- language = locale.getLanguage();
- }
- MyVersion version = new MyVersion(language, RestUtils.checkLocalDesktopAvailable(requestContext) == null);
- String json = JsonUtils.toJson(version, prettyPrinter);
- return json;
- }
-
- public class MyVersion {
- private Version version;
- private String language;
- private boolean localDesktopAvailable;
-
- private MyVersion(String language, boolean localDesktopAvailable) {
- this.version = Version.getInstance();
- this.language = language;
- this.localDesktopAvailable = localDesktopAvailable;
- }
-
- public String getAppName() {
- return version.getAppName();
- }
-
- public String getVersion() {
- return version.getVersion();
- }
-
- public String getBuildDateUTC() {
- return version.getBuildDateUTC();
- }
-
- public Date getBuildDate() {
- return version.getBuildDate();
- }
-
- /**
- * @return Version of the available update, if exist. Otherwise null.
- */
- public String getUpdateVersion() {
- return version.getUpdateVersion();
- }
-
- public String getLanguage() {
- return language;
- }
-
- public boolean isLocalDesktopAvailable() {
- return localDesktopAvailable;
- }
- }
-}
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/SingleUserManager.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/SingleUserManager.java
index 0391b8d..233016d 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/SingleUserManager.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/SingleUserManager.java
@@ -48,7 +48,11 @@
String dateFormat = userData.getDateFormat();
this.singleUser.setDateFormat(dateFormat);
String lang = Languages.asString(locale);
- preferences.put(USER_LOCAL_PREF_KEY, lang);
+ if (lang != null) {
+ preferences.put(USER_LOCAL_PREF_KEY, lang);
+ } else {
+ preferences.remove(USER_LOCAL_PREF_KEY);
+ }
try {
preferences.flush();
} catch (BackingStoreException ex) {
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
index 9e03d68..87ae77e 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
@@ -1,9 +1,12 @@
package de.micromata.borgbutler.server.user;
+import de.micromata.borgbutler.server.rest.RequestLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
@@ -12,6 +15,7 @@
* <br>
* For requests from remote (not localhost) an exception is thrown due to security reasons.
*/
+@Component
public class UserFilter implements Filter {
private Logger log = LoggerFactory.getLogger(UserFilter.class);
@@ -41,6 +45,7 @@
userData = UserManager.instance().getUser("dummy");
UserUtils.setUser(userData, request.getLocale());
if (log.isDebugEnabled()) log.debug("Request for user: " + userData);
+ log.info("Request for user: " + userData + ": " + RequestLog.asString((HttpServletRequest) request));
chain.doFilter(request, response);
} finally {
UserUtils.removeUser();
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/BorgButlerApplication.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/BorgButlerApplication.kt
new file mode 100644
index 0000000..f261fcd
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/BorgButlerApplication.kt
@@ -0,0 +1,179 @@
+package de.micromata.borgbutler.server
+
+import de.micromata.borgbutler.cache.ButlerCache
+import de.micromata.borgbutler.config.ConfigurationHandler.Companion.init
+import de.micromata.borgbutler.config.ConfigurationHandler.Companion.setConfigClazz
+import de.micromata.borgbutler.server.user.SingleUserManager
+import de.micromata.borgbutler.server.user.UserManager
+import mu.KotlinLogging
+import org.apache.commons.cli.*
+import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
+import org.apache.commons.io.FileUtils
+import org.apache.commons.io.FilenameUtils
+import org.apache.commons.lang3.StringUtils
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.SpringApplication
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.context.event.ApplicationReadyEvent
+import org.springframework.context.event.EventListener
+import java.awt.Desktop
+import java.io.*
+import java.net.URI
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.*
+import javax.annotation.PreDestroy
+
+private val log = KotlinLogging.logger {}
+
+@SpringBootApplication
+open class BorgButlerApplication {
+
+ @Value("\${server.address}")
+ private var serverAddress: String = "127.0.0.1"
+
+ @Value("\${server.port}")
+ private var serverPort = 9042
+
+ private fun _start(args: Array<out String>) {
+ setConfigClazz(ServerConfiguration::class.java)
+ // create Options object
+ val options = Options()
+ options.addOption(
+ "e",
+ "extract-archive-content",
+ true,
+ "Extracts the content of an archive cache file only (doesn't start the server). A complete file list of the archive will be extracted to stdout."
+ )
+ options.addOption("p", "port", true, "The default port for the web server.")
+ options.addOption("q", "quiet", false, "Don't open browser automatically.")
+ options.addOption("h", "help", false, "Print this help screen.")
+ //options.addOption("homeDir", true, "Specify own home directory of butler. Default is $HOME/.borgbutler");
+ val parser: CommandLineParser = DefaultParser()
+ try {
+ // parse the command line arguments
+ val line = parser.parse(options, args)
+ if (line.hasOption('h')) {
+ printHelp(options)
+ return
+ }
+ if (line.hasOption('e')) {
+ val file = line.getOptionValue("e")
+ printArchiveContent(file)
+ return
+ }
+ if (line.hasOption('p')) {
+ // initialise the member variable
+ val portString = line.getOptionValue("p")
+ try {
+ val port = portString.toInt()
+ if (port < 1 || port > 65535) {
+ System.err.println("Port outside range.")
+ return
+ }
+ ServerConfiguration.get().port = port
+ } catch (ex: NumberFormatException) {
+ printHelp(options)
+ return
+ }
+ }
+ val applicationHome = System.getProperty("borgbutlerHome")
+ if (applicationHome != null) {
+ init(applicationHome)
+ }
+ if (Desktop.isDesktopSupported()) {
+ RunningMode.setServerType(RunningMode.ServerType.DESKTOP)
+ } else {
+ RunningMode.setServerType(RunningMode.ServerType.SERVER)
+ }
+ RunningMode.logMode()
+
+ UserManager.setUserManager(SingleUserManager())
+
+ BorgInstallation.getInstance().initialize()
+
+ // 0.0.0.0 for Docker installations.
+ val url = "http://$serverAddress:$serverPort/".replace("0.0.0.0", "127.0.0.1")
+ if (!line.hasOption('q')) {
+ try {
+ Desktop.getDesktop().browse(URI.create(url))
+ } catch (ex: Exception) {
+ log.info("Can't open web browser: " + ex.message)
+ }
+ } else {
+ log.info("Please open your browser: $url")
+ }
+ } catch (ex: ParseException) {
+ // oops, something went wrong
+ System.err.println("Parsing failed. Reason: " + ex.message)
+ printHelp(options)
+ }
+ }
+
+ @EventListener(ApplicationReadyEvent::class)
+ open fun startApp() {
+ }
+
+ @PreDestroy
+ open fun shutdownApp() {
+ log.info("Shutting down BorgButler web server...")
+ ButlerCache.getInstance().shutdown()
+ }
+
+ companion object {
+ private val main = BorgButlerApplication()
+
+ @JvmStatic
+ fun main(vararg args: String) {
+ main._start(args)
+ SpringApplication.run(BorgButlerApplication::class.java, *args)
+ }
+
+ private fun printHelp(options: Options) {
+ val formatter = HelpFormatter()
+ formatter.printHelp("borgbutler-server", options)
+ }
+
+ private fun printArchiveContent(fileName: String) {
+ val file = File(fileName)
+ val fileList = ButlerCache.getInstance().getArchiveContent(file)
+ var parseFormatExceptionPrinted = false
+ if (fileList != null && fileList.size > 0) {
+ val tz = TimeZone.getTimeZone("UTC")
+ val iso: DateFormat =
+ SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'") // Quoted "Z" to indicate UTC, no timezone offset
+ iso.timeZone = tz
+ val df: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S")
+ val out = File(FilenameUtils.getBaseName(fileName) + ".txt.gz")
+ log.info("Writing file list to: " + out.absolutePath)
+ try {
+ PrintWriter(BufferedOutputStream(GzipCompressorOutputStream(FileOutputStream(out)))).use { writer ->
+ for (item in fileList) {
+ var time = item.mtime
+ if (time.indexOf('T') > 0) {
+ try {
+ val date = df.parse(item.mtime)
+ time = iso.format(date)
+ } catch (ex: java.text.ParseException) {
+ if (!parseFormatExceptionPrinted) {
+ parseFormatExceptionPrinted = true
+ log.error("Can't parse date: " + item.mtime)
+ }
+ }
+ }
+ writer.write(
+ item.mode + " " + item.user + " "
+ + StringUtils.rightPad(FileUtils.byteCountToDisplaySize(item.size), 10)
+ + " " + time + " " + item.path
+ )
+ writer.write("\n")
+ }
+ }
+ } catch (ex: IOException) {
+ log.error("Can't write file '" + out.absolutePath + "': " + ex.message)
+ }
+ }
+ // 2018-12-04T22:44:58.924642
+ }
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/ServerConfiguration.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/ServerConfiguration.kt
new file mode 100644
index 0000000..cbfcc81
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/ServerConfiguration.kt
@@ -0,0 +1,50 @@
+package de.micromata.borgbutler.server
+
+import de.micromata.borgbutler.config.Configuration
+import de.micromata.borgbutler.config.ConfigurationHandler.Companion.getConfiguration
+import mu.KotlinLogging
+import org.apache.commons.lang3.StringUtils
+import org.slf4j.LoggerFactory
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.stereotype.Component
+
+private val log = KotlinLogging.logger {}
+
+class ServerConfiguration : Configuration() {
+ var port = WEBSERVER_PORT_DEFAULT
+
+ /**
+ * If true, CrossOriginFilter will be set.
+ */
+ var isWebDevelopmentMode = WEB_DEVELOPMENT_MODE_PREF_DEFAULT
+
+ fun copyFrom(other: ServerConfiguration) {
+ super.copyFrom(other)
+ port = other.port
+ isWebDevelopmentMode = other.isWebDevelopmentMode
+ }
+
+ companion object {
+ val supportedLanguages = arrayOf("en", "de")
+ const val WEBSERVER_PORT_DEFAULT = 9042
+ private const val WEB_DEVELOPMENT_MODE_PREF_DEFAULT = false
+ @JvmStatic
+ var applicationHome: String? = null
+ get() {
+ if (field == null) {
+ field = System.getProperty("applicationHome")
+ if (StringUtils.isBlank(field)) {
+ field = System.getProperty("user.dir")
+ log.info("applicationHome is not given as JVM parameter. Using current working dir (OK for start in IDE): $field")
+ }
+ }
+ return field
+ }
+ private set
+
+ @JvmStatic
+ fun get(): ServerConfiguration {
+ return getConfiguration() as ServerConfiguration
+ }
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/WebConfig.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/WebConfig.kt
new file mode 100644
index 0000000..b24752a
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/WebConfig.kt
@@ -0,0 +1,30 @@
+package de.micromata.borgbutler.server
+
+import mu.KotlinLogging
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Configuration
+import org.springframework.web.servlet.config.annotation.CorsRegistry
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
+
+
+private val log = KotlinLogging.logger {}
+
+@Configuration
+@EnableWebMvc
+open class WebConfig : WebMvcConfigurer {
+ override fun addCorsMappings(registry: CorsRegistry) {
+ if (ServerConfiguration.get().isWebDevelopmentMode) {
+ log.warn("*********************************")
+ log.warn("*********** **********")
+ log.warn("*********** ATTENTION! **********")
+ log.warn("*********** **********")
+ log.warn("*********** Running in **********")
+ log.warn("*********** dev mode! **********")
+ log.warn("*********** **********")
+ log.warn("*********************************")
+ log.warn("Don't deliver this app in dev mode due to security reasons (CrossOriginFilter is set)!")
+ registry.addMapping("/**")
+ }
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/logging/LoggerMemoryAppender.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/logging/LoggerMemoryAppender.kt
new file mode 100644
index 0000000..cf5a57a
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/logging/LoggerMemoryAppender.kt
@@ -0,0 +1,134 @@
+package de.micromata.borgbutler.server.logging
+
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.AppenderBase
+import mu.KotlinLogging
+import org.apache.commons.lang3.StringUtils
+import java.util.*
+
+private val log = KotlinLogging.logger {}
+
+class LoggerMemoryAppender : AppenderBase<ILoggingEvent?>() {
+ private var lastLogEntryOrderNumber = -1
+
+ var queue = FiFoBuffer<LoggingEventData>(QUEUE_SIZE)
+
+ override fun append(event: ILoggingEvent?) {
+ val eventData = LoggingEventData(event)
+ eventData.orderNumber = ++lastLogEntryOrderNumber
+ queue.add(eventData)
+ }
+
+ /**
+ * For testing purposes.
+ *
+ * @param event
+ */
+ fun append(event: LoggingEventData) {
+ queue.add(event)
+ }
+
+ fun query(filter: LogFilter?, locale: Locale?): List<LoggingEventData> {
+ val result: MutableList<LoggingEventData> = ArrayList()
+ if (filter == null) {
+ return result
+ }
+ var maxSize = if (filter.maxSize != null) filter.maxSize else MAX_RESULT_SIZE
+ if (maxSize > MAX_RESULT_SIZE) {
+ maxSize = MAX_RESULT_SIZE
+ }
+ var counter = 0
+ //I18n i18n = CoreI18n.getDefault().get(locale);
+ if (filter.isAscendingOrder) {
+ for (i in 0 until queue.size) {
+ val resultEvent = getResultEvent(filter, queue[i], locale) ?: continue
+ result.add(resultEvent)
+ if (++counter > maxSize) break
+ }
+ } else {
+ for (i in queue.size downTo 0) {
+ val resultEvent = getResultEvent(filter, queue[i], locale) ?: continue
+ result.add(resultEvent)
+ if (++counter > maxSize) break
+ }
+ }
+ return result
+ }
+
+ private fun getResultEvent(filter: LogFilter, event: LoggingEventData?, locale: Locale?): LoggingEventData? {
+ if (event == null) {
+ return null
+ }
+ if (!event.getLevel().matches(filter.threshold)) {
+ return null
+ }
+ if (filter.lastReceivedLogOrderNumber != null) {
+ if (event.getOrderNumber() <= filter.lastReceivedLogOrderNumber) {
+ return null
+ }
+ }
+ var logString: String? = null
+ val message = event.getMessage()
+ val localizedMessage = false
+ /*if (message != null && message.startsWith("i18n=")) {
+ I18nLogEntry i18nLogEntry = I18nLogEntry.parse(message);
+ message = i18n.formatMessage(i18nLogEntry.getI18nKey(), (Object[])i18nLogEntry.getArgs());
+ localizedMessage = true;
+ }*/if (StringUtils.isNotBlank(filter.search)) {
+ val sb = StringBuilder()
+ sb.append(event.logDate)
+ append(sb, event.getLevel(), true)
+ append(sb, message, true)
+ append(sb, event.getJavaClass(), true)
+ append(sb, event.stackTrace, filter.isShowStackTraces)
+ logString = sb.toString()
+ }
+ if (logString == null || matches(logString, filter.search)) {
+ var resultEvent: LoggingEventData = event
+ if (localizedMessage) {
+ // Need a clone
+ resultEvent = event.clone()
+ resultEvent.setMessage(message)
+ }
+ return resultEvent
+ }
+ return null
+ }
+
+ private fun append(sb: StringBuilder, value: Any?, append: Boolean) {
+ if (!append || value == null) {
+ return
+ }
+ sb.append("|#|").append(value)
+ }
+
+ private fun matches(str: String, searchString: String): Boolean {
+ if (StringUtils.isBlank(str)) {
+ return StringUtils.isBlank(searchString)
+ }
+ return if (StringUtils.isBlank(searchString)) {
+ true
+ } else str.toLowerCase().contains(searchString.toLowerCase())
+ }
+
+ companion object {
+ private const val MAX_RESULT_SIZE = 1000
+ private const val QUEUE_SIZE = 10000
+ private var instance: LoggerMemoryAppender? = null
+
+ fun getInstance(): LoggerMemoryAppender {
+ return instance!!
+ }
+ }
+
+ /**
+ * Initialized by logback on start-up (see logback-spring.xml).
+ */
+ init {
+ if (instance != null) {
+ log.warn { "*** LoggerMemoryAppender instantiated twice! Shouldn't occur. ***" }
+ } else {
+ instance = this
+ }
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ArchivesRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ArchivesRest.kt
new file mode 100644
index 0000000..b51921b
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ArchivesRest.kt
@@ -0,0 +1,221 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.BorgCommands
+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.json.JsonUtils
+import de.micromata.borgbutler.json.borg.BorgFilesystemItem
+import de.micromata.borgbutler.utils.DirUtils
+import mu.KotlinLogging
+import org.apache.commons.collections4.CollectionUtils
+import org.apache.commons.lang3.StringUtils
+import org.apache.commons.lang3.math.NumberUtils
+import org.apache.coyote.Response
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import java.awt.Desktop
+import java.io.File
+import java.io.IOException
+import java.nio.file.Files
+import java.nio.file.Path
+
+private val log = KotlinLogging.logger {}
+
+@RestController
+@RequestMapping("/rest/archives")
+class ArchivesRest {
+ /**
+ * @param repoName Name of repository ([Repository.getName].
+ * @param archiveId 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
+ */
+ @GetMapping
+ fun getArchive(
+ @RequestParam("repo") repoName: String,
+ @RequestParam("archiveId") archiveId: String,
+ @RequestParam("force", required = false) force: Boolean,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean
+ ): String {
+ val archive: Archive = ButlerCache.getInstance().getArchive(repoName, archiveId, force == true)
+ if (force == true) {
+ ButlerCache.getInstance().deleteCachedArchiveContent(repoName, archiveId)
+ }
+ return JsonUtils.toJson(archive, prettyPrinter == true)
+ }
+
+ /**
+ * @param archiveId Id or name of archive.
+ * @param searchString The string to search for (key words separated by white chars, trailing ! char represents exclude).
+ * @param mode Flat (default) or tree.
+ * @param currentDirectory The current displayed directory (only files and directories contained will be returned).
+ * @param maxResultSize maximum number of file items to return (default is 50).
+ * @param diffArchiveId If given, the differences between archiveId and diffArchiveId will be returned.
+ * @param autoChangeDirectoryToLeafItem If given, this method will step automatically into single sub directories.
+ * @param force If false (default), non cached file lists will not be loaded by borg.
+ * @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
+ */
+ @GetMapping("filelist")
+ fun getArchiveFileList(
+ @RequestParam("archiveId") archiveId: String,
+ @RequestParam("searchString", required = false) searchString: String?,
+ @RequestParam("mode", required = false) mode: String?,
+ @RequestParam("currentDirectory", required = false) currentDirectory: String?,
+ @RequestParam("maxResultSize", required = false) maxResultSize: String?,
+ @RequestParam("diffArchiveId", required = false) diffArchiveId: String?,
+ @RequestParam("autoChangeDirectoryToLeafItem", required = false) autoChangeDirectoryToLeafItem: Boolean?,
+ @RequestParam("force", required = false) force: Boolean?,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?
+ ): String {
+ val diffMode = StringUtils.isNotBlank(diffArchiveId)
+ val maxSize = NumberUtils.toInt(maxResultSize, 50)
+ val filter = if (diffMode) DiffFileSystemFilter() else FileSystemFilter()
+ filter.setSearchString(searchString)
+ .setCurrentDirectory(currentDirectory)
+ .setAutoChangeDirectoryToLeafItem(autoChangeDirectoryToLeafItem == true)
+ var items: List<BorgFilesystemItem>?
+ if (diffMode) {
+ filter.setMode(FileSystemFilter.Mode.FLAT)
+ items = ButlerCache.getInstance().getArchiveContent(archiveId, true, filter)
+ val diffItems: List<BorgFilesystemItem> = ButlerCache.getInstance().getArchiveContent(
+ diffArchiveId, true,
+ filter
+ )
+ filter.setMaxResultSize(maxSize)
+ .setMode(mode)
+ items = (filter as DiffFileSystemFilter).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 == true,
+ filter
+ )
+ if (items == null) {
+ return "[{\"mode\": \"notLoaded\"}]"
+ }
+ }
+ return JsonUtils.toJson(items, prettyPrinter == true)
+ }
+
+ /**
+ * @param archiveId
+ * @param openDownloads
+ * @param fileNumber The fileNumber of the file or directory in the archive served by BorgButler's
+ */
+ @GetMapping("/restore")
+ fun restore(
+ @RequestParam("archiveId") archiveId: String,
+ @RequestParam("openDownloads", required = false) openDownloads: Boolean?,
+ @RequestParam("fileNumber") fileNumber: Int?
+ ): ResponseEntity<*> {
+ log.info("Requesting file #$fileNumber of archive '$archiveId'.")
+ val filter: FileSystemFilter = FileSystemFilter().setFileNumber(fileNumber)
+ val items: List<BorgFilesystemItem> = ButlerCache.getInstance().getArchiveContent(
+ archiveId, false,
+ filter
+ )
+ if (CollectionUtils.isEmpty(items)) {
+ log.error(
+ "Requested file #" + fileNumber + " not found in archive '" + archiveId
+ + ". (May-be the archive content isn't yet loaded to the cache."
+ )
+ return RestUtils.notFound()
+ }
+ if (items.size != 1) {
+ log.error(
+ "Requested file #" + fileNumber + " found multiple times (" + items.size + ") in archive '" + archiveId
+ + "! Please remove the archive files (may-be corrupted)."
+ )
+ return RestUtils.notFound()
+ }
+ val archive: Archive = ButlerCache.getInstance().getArchive(archiveId) ?: return RestUtils.notFound()
+
+ val repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(archive.repoId)
+ try {
+ val item: BorgFilesystemItem = items[0]
+ val restoreHomeDir: File = ConfigurationHandler.getConfiguration().getRestoreHomeDir()
+ val restoreDir: File = BorgCommands.extractFiles(restoreHomeDir, repoConfig, archive, item.getPath())
+ val files: List<Path?> = DirUtils.listFiles(restoreDir.toPath())
+ if (CollectionUtils.isEmpty(files)) {
+ log.error("No files extracted.")
+ return RestUtils.notFound()
+ }
+ if (openDownloads == true) openFileBrowser(File(restoreDir, item.getPath()))
+ return ResponseEntity.ok("OK")
+ } catch (ex: IOException) {
+ log.error("No file extracted: " + ex.message, ex)
+ return RestUtils.notFound()
+ }
+ }
+
+ private fun openFileBrowser(fileDirectory: File) {
+ if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
+ var file: File? = fileDirectory
+ if (!fileDirectory.exists() || Files.isSymbolicLink(fileDirectory.toPath())) {
+ // Open parent.
+ file = fileDirectory.parentFile
+ }
+ Desktop.getDesktop().browseFileDirectory(file)
+ }
+ }
+
+ private fun handleRestoredFiles(repoConfig: BorgRepoConfig, archive: Archive): Response? {
+ // Todo: Handle download of single files as well as download of zip archive (if BorgButler runs remote).
+ return null
+ /* File file = path.toFile();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ FileUtils.copyFile(file, baos);
+ } catch (IOException ex) {
+ log.error(ex.getMessage(), ex);
+ }
+ BorgFilesystemItem item = items.get(0);
+ file = new File(item.getPath());
+ byte[] byteArray = baos.toByteArray();//result.getAsByteArrayOutputStream().toByteArray();
+ Response.ResponseBuilder builder = Response.ok(byteArray);
+ builder.header("Content-Disposition", "attachment; filename=" + file.getName());
+ // Needed to get the Content-Disposition by client:
+ builder.header("Access-Control-Expose-Headers", "Content-Disposition");
+ Response response = builder.build();
+ return response;
+
+ try {
+ //java.nio.file.Path tempDirWithPrefix = Files.createTempDirectory("borgbutler-extract-");
+ File restoreHomeDir = ConfigurationHandler.getConfiguration().getRestoreHomeDir();
+ File restoreDir = BorgCommands.extractFiles(restoreHomeDir, repoConfig, archive.getName(), item.getPath());
+ openFileBrowser(restoreDir);
+ List<java.nio.file.Path> files = DirUtils.listFiles(tempDir);
+ if (CollectionUtils.isEmpty(files)) {
+ log.error("No file extracted.");
+ Response.ResponseBuilder builder = Response.status(404);
+ return builder.build();
+ }
+ path = files.get(0);
+ } catch (IOException ex) {
+ log.error("No file extracted: " + ex.getMessage(), ex);
+ Response.ResponseBuilder builder = Response.status(404);
+ return builder.build();
+ } finally {
+ if (tempDir != null) {
+ try {
+ FileUtils.deleteDirectory(tempDir.toFile());
+ } catch (IOException ex) {
+ log.error("Error while trying to delete temporary directory '" + tempDir.toString() + "': " + ex.getMessage(), ex);
+ }
+ }
+ }*/
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.kt
new file mode 100644
index 0000000..f392664
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.kt
@@ -0,0 +1,82 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.BorgCommandResult
+import de.micromata.borgbutler.BorgCommands
+import de.micromata.borgbutler.cache.ButlerCache
+import de.micromata.borgbutler.config.BorgRepoConfig
+import de.micromata.borgbutler.config.ConfigurationHandler
+import de.micromata.borgbutler.data.Repository
+import de.micromata.borgbutler.jobs.JobResult
+import de.micromata.borgbutler.json.JsonUtils
+import mu.KotlinLogging
+import org.springframework.web.bind.annotation.*
+
+private val log = KotlinLogging.logger {}
+
+@RestController
+@RequestMapping("/rest/repoConfig")
+class BorgRepoConfigsRest {
+ /**
+ * @param id id or name of repo.
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @return [BorgRepoConfig] as json string.
+ * @see JsonUtils.toJson
+ */
+ @GetMapping
+ fun getRepoConfig(
+ @RequestParam("id") id: String,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?
+ ): String {
+ val repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(id)
+ return JsonUtils.toJson(repoConfig, prettyPrinter == true)
+ }
+
+ @PostMapping
+ fun setRepoConfig(@RequestBody newRepoConfig: BorgRepoConfig) {
+ if ("new" == newRepoConfig.getId()) {
+ newRepoConfig.setId(null)
+ ConfigurationHandler.getConfiguration().add(newRepoConfig)
+ } else if ("init" == newRepoConfig.getId()) {
+ newRepoConfig.setId(null)
+ ConfigurationHandler.getConfiguration().add(newRepoConfig)
+ } else {
+ val repoConfig: BorgRepoConfig? =
+ ConfigurationHandler.getConfiguration().getRepoConfig(newRepoConfig.getId())
+ if (repoConfig == null) {
+ log.error("Can't find repo config '" + newRepoConfig.getId() + "'. Can't save new settings.")
+ return
+ }
+ ButlerCache.getInstance().clearRepoCacheAccess(repoConfig.getRepo())
+ ButlerCache.getInstance().clearRepoCacheAccess(newRepoConfig.getRepo())
+ repoConfig.copyFrom(newRepoConfig)
+ }
+ ConfigurationHandler.getInstance().save()
+ }
+
+ /**
+ * @param idOrName id or name of repo to remove from BorgButler.
+ * @return "OK" if removed or error string.
+ */
+ @GetMapping("remove")
+ fun removeRepoConfig(@RequestParam("id") idOrName: String): String {
+ val result: Boolean = ConfigurationHandler.getConfiguration().remove(idOrName)
+ if (!result) {
+ val error = "Repo config with id or name '$idOrName' not found. Can't remove the repo."
+ log.error(error)
+ return error
+ }
+ ConfigurationHandler.getInstance().save()
+ return "OK"
+ }
+
+ /**
+ * @param jsonRepoConfig All configuration value of the repo to check.
+ * @return Result of borg (tbd.).
+ */
+ @PostMapping("check")
+ fun checkConfig(@RequestBody repoConfig: BorgRepoConfig): String {
+ log.info("Testing repo config: $repoConfig")
+ val result: BorgCommandResult<Repository> = BorgCommands.info(repoConfig)
+ return if (result.getStatus() == JobResult.Status.OK) "OK" else result.getError()
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationInfo.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationInfo.kt
new file mode 100644
index 0000000..a5e0b8f
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationInfo.kt
@@ -0,0 +1,9 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.server.BorgVersion
+import de.micromata.borgbutler.server.ServerConfiguration
+
+class ConfigurationInfo(
+ var serverConfiguration: ServerConfiguration? = null,
+ var borgVersion: BorgVersion? = null
+)
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationRest.kt
new file mode 100644
index 0000000..c0ac4b2
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ConfigurationRest.kt
@@ -0,0 +1,76 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.cache.ButlerCache
+import de.micromata.borgbutler.config.ConfigurationHandler
+import de.micromata.borgbutler.json.JsonUtils
+import de.micromata.borgbutler.server.BorgInstallation
+import de.micromata.borgbutler.server.ServerConfiguration
+import de.micromata.borgbutler.server.user.UserData
+import de.micromata.borgbutler.server.user.UserManager
+import mu.KotlinLogging
+import org.apache.commons.lang3.StringUtils
+import org.springframework.web.bind.annotation.*
+
+private val log = KotlinLogging.logger {}
+
+@RestController
+@RequestMapping("/rest/configuration")
+class ConfigurationRest {
+
+ /**
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @see JsonUtils.toJson
+ */
+ @GetMapping("config")
+ fun getConfig(@RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String {
+ val configurationInfo = ConfigurationInfo()
+ configurationInfo.serverConfiguration = ServerConfiguration.get()
+ configurationInfo.borgVersion = BorgInstallation.getInstance().getBorgVersion()
+ return JsonUtils.toJson(configurationInfo, prettyPrinter)
+ }
+
+ @PostMapping("config")
+ fun setConfig(@RequestBody configurationInfo: ConfigurationInfo) {
+ val configurationHandler = ConfigurationHandler.getInstance()
+ BorgInstallation.getInstance()
+ .configure(configurationInfo.serverConfiguration, configurationInfo.borgVersion?.borgBinary)
+ val configuration: ServerConfiguration = ServerConfiguration.get()
+ configurationInfo.serverConfiguration?.let {
+ configuration.copyFrom(it)
+ }
+ configurationHandler.save()
+ }
+
+ /**
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @see JsonUtils.toJson
+ */
+ @GetMapping("user")
+ fun getUser(@RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String {
+ val user: UserData = RestUtils.getUser()
+ return JsonUtils.toJson(user, prettyPrinter)
+ }
+
+ @PostMapping("user")
+ fun setUser(@RequestBody user: UserData) {
+ if (user.getLocale() != null && StringUtils.isBlank(user.getLocale().getLanguage())) {
+ // Don't set locale with "" as language.
+ user.setLocale(null)
+ }
+ if (StringUtils.isBlank(user.getDateFormat())) {
+ // Don't set dateFormat as "".
+ user.setDateFormat(null)
+ }
+ UserManager.instance().saveUser(user)
+ }
+
+ /**
+ * Resets the settings to default values (deletes all settings).
+ */
+ @GetMapping("clearAllCaches")
+ fun clearAllCaches(): String {
+ log.info("Clear all caches called...")
+ ButlerCache.getInstance().clearAllCaches()
+ return "OK"
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ExceptionStackTracePrinter.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ExceptionStackTracePrinter.kt
new file mode 100644
index 0000000..9c20d1a
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ExceptionStackTracePrinter.kt
@@ -0,0 +1,72 @@
+/////////////////////////////////////////////////////////////////////////////
+//
+// Project ProjectForge Community Edition
+// www.projectforge.org
+//
+// Copyright (C) 2001-2021 Micromata GmbH, Germany (www.micromata.com)
+//
+// ProjectForge is dual-licensed.
+//
+// This community edition is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; version 3 of the License.
+//
+// This community edition is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+// Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see http://www.gnu.org/licenses/.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package de.micromata.borgbutler.server.rest
+
+/**
+ * Prints stack-trace without foreign packages in much shorter form than log.error(ex.message, ex) does.
+ * @author Kai Reinhard (k.reinhard@micromata.de)
+ */
+object ExceptionStackTracePrinter {
+
+ /**
+ * @param showExceptionMessage If true, the exception message itself will be prepended to stack trace.
+ * @param stopBeforeForeignPackages If true, after showing stack trace elements of own package 'org.projectforge' any further element is hidden if foreign stack element is reached.
+ * @param depth Maximum depth of stack trace elements to show (default is 10).
+ * @param showPackagesOnly Only stack elements started with one of these string is classified as own package to print. If nothing given, all 'org.projectforge' classes are
+ * classified as own packages.
+ */
+ @JvmStatic
+ @JvmOverloads
+ fun toString(ex: Exception, showExceptionMessage: Boolean = true, stopBeforeForeignPackages: Boolean = true, depth: Int = 10, vararg showPackagesOnly: String): String {
+ val sb = StringBuilder()
+ if (showExceptionMessage) {
+ sb.append(ex::class.java.name).append(":").append(ex.message).append("\n")
+ }
+ var counter = 0
+ val showPackages = if (showPackagesOnly.isEmpty()) OWN_PACKAGES else showPackagesOnly
+ var placeHolderPrinted = false
+ var ownStackelementsPrinted = false
+ for (element in ex.stackTrace) {
+ if (!showPackages.any { element.className.startsWith(it) }) {
+ if (ownStackelementsPrinted && stopBeforeForeignPackages) {
+ sb.append("...following foreign packages are hidden...\n")
+ break
+ }
+ if (!placeHolderPrinted) {
+ sb.append("...(foreign packages are hidden)...\n")
+ placeHolderPrinted = true
+ }
+ continue // Don't show foreign class entries.
+ }
+ ownStackelementsPrinted = true
+ sb.append("at ${element.className}.${element.methodName} (${element.fileName}:${element.lineNumber})\n")
+ if (++counter >= depth) {
+ break
+ }
+ }
+ return sb.toString()
+ }
+
+ val OWN_PACKAGES = arrayOf("de.micromata.borgbutler")
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.kt
new file mode 100644
index 0000000..afa4896
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/FilesystemBrowserRest.kt
@@ -0,0 +1,141 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.json.JsonUtils
+import de.micromata.borgbutler.server.RunningMode
+import mu.KotlinLogging
+import org.apache.commons.lang3.StringUtils
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import java.awt.Color
+import java.awt.FileDialog
+import java.io.File
+import javax.servlet.http.HttpServletRequest
+import javax.swing.JFileChooser
+import javax.swing.JFrame
+import javax.swing.JLabel
+import javax.swing.SwingConstants
+
+private val log = KotlinLogging.logger {}
+
+@RestController
+@RequestMapping("/rest/files")
+class FilesystemBrowserRest {
+
+ /**
+ * Opens a directory browser or file browser on the desktop app and returns the chosen dir/file. Works only if Browser and Desktop app are running
+ * on the same host.
+ *
+ * @param current The current path of file. If not given the directory/file browser starts with the last used directory or user.home.
+ * @return The chosen directory path (absolute path).
+ */
+ @GetMapping("/browse-local-filesystem")
+ fun browseLocalFilesystem(
+ request: HttpServletRequest,
+ @RequestParam("current", required = false) current: String?
+ ): String {
+ val msg = RestUtils.checkLocalDesktopAvailable(request)
+ if (msg != null) {
+ log.info(msg)
+ return msg
+ }
+ if (fileDialog != null || fileChooser != null) {
+ log.warn("Cannot call already opened file choose twice. Close file chooser first.")
+ return "{\"directory\": \"\"}"
+ }
+ var file: File? = null
+ synchronized(FilesystemBrowserRest::class.java) {
+ if (frame == null) {
+ val fr = JFrame("BorgButler")
+ frame = fr
+ fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
+ fr.setSize(300, 100)
+ fr.setResizable(false)
+ fr.setLocationRelativeTo(null)
+ fr.setBackground(Color.WHITE)
+ fr.getContentPane().setBackground(Color.WHITE)
+ val label = JLabel("Click for choosing directory...", SwingConstants.CENTER)
+ fr.add(label)
+ }
+ if (RunningMode.getOSType() == RunningMode.OSType.MAC_OS) {
+ // The JFileChooser will hang after several calls, use AWT file dialog instead for Mac OS:
+ System.setProperty("apple.awt.fileDialogForDirectories", "true")
+ frame?.let {
+ it.setAlwaysOnTop(true)
+ it.setVisible(true)
+ }
+ try {
+ val dialog =
+ FileDialog(frame, "Choose a directory", FileDialog.LOAD)
+ fileDialog = dialog
+ if (StringUtils.isNotBlank(current)) {
+ dialog.setDirectory(current)
+ }
+ dialog.toFront()
+ dialog.setVisible(true)
+ val filename: String? = dialog.getFile()
+ val directory: String? = dialog.getDirectory()
+ dialog.setVisible(false)
+ if (filename == null) {
+ return ""
+ }
+ file = File(directory, filename)
+ if (file?.isDirectory != true) {
+ file = File(directory)
+ }
+ } finally {
+ fileDialog = null
+ }
+ } else {
+ try {
+ val chooser = if (StringUtils.isNotBlank(current)) {
+ JFileChooser(current)
+ } else {
+ JFileChooser()
+ }
+ fileChooser = chooser
+ chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
+ frame?.let {
+ it.setVisible(true)
+ it.setAlwaysOnTop(true)
+ }
+ val returnCode: Int = chooser.showDialog(
+ frame,
+ "Choose"
+ )
+ frame?.let {
+ it.setVisible(false)
+ it.setAlwaysOnTop(false)
+ }
+ if (returnCode == JFileChooser.APPROVE_OPTION) {
+ file = chooser.getSelectedFile()
+ }
+ } finally {
+ fileChooser = null
+ }
+ }
+ }
+ val filename = if (file != null) JsonUtils.toJson(file!!.absolutePath) else ""
+ return "{\"directory\":\"$filename\"}"
+ }
+
+ /**
+ * @return OK, if the local desktop services such as open file browser etc. are available.
+ */
+ @GetMapping("/local-fileservices-available")
+ fun browseLocalFilesystem(request: HttpServletRequest): String {
+ val msg = RestUtils.checkLocalDesktopAvailable(request)
+ if (msg != null) {
+ log.info(msg)
+ return msg
+ }
+ return "OK"
+ }
+
+ companion object {
+ private var frame: JFrame? = null
+ private var fileDialog: FileDialog? = null
+ private var fileChooser: JFileChooser? = null
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/GlobalDefaultExceptionHandling.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/GlobalDefaultExceptionHandling.kt
new file mode 100644
index 0000000..4681483
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/GlobalDefaultExceptionHandling.kt
@@ -0,0 +1,57 @@
+/////////////////////////////////////////////////////////////////////////////
+//
+// Project ProjectForge Community Edition
+// www.projectforge.org
+//
+// Copyright (C) 2001-2021 Micromata GmbH, Germany (www.micromata.com)
+//
+// ProjectForge is dual-licensed.
+//
+// This community edition is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; version 3 of the License.
+//
+// This community edition is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+// Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see http://www.gnu.org/licenses/.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package de.micromata.borgbutler.server.rest
+
+import mu.KotlinLogging
+import org.springframework.core.annotation.AnnotationUtils
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.ControllerAdvice
+import org.springframework.web.bind.annotation.ExceptionHandler
+import org.springframework.web.bind.annotation.ResponseStatus
+import javax.servlet.http.HttpServletRequest
+
+private val log = KotlinLogging.logger {}
+
+@ControllerAdvice
+internal class GlobalDefaultExceptionHandler {
+ @ExceptionHandler(value = [(Exception::class)])
+ @Throws(Exception::class)
+ fun defaultErrorHandler(request: HttpServletRequest, ex: Exception): Any {
+ // If the exception is annotated with @ResponseStatus rethrow it and let
+ // the framework handle it.
+ if (AnnotationUtils.findAnnotation(ex.javaClass, ResponseStatus::class.java) != null) {
+ throw ex
+ }
+ log.error(
+ "Exception while processing request: ${ex.message} Request: ${RequestLog.asJson(request)},\nexception=${ex.message}\n${
+ ExceptionStackTracePrinter.toString(
+ ex,
+ false
+ )
+ }"
+ )
+ return ResponseEntity("Internal error.", HttpStatus.BAD_REQUEST)
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/I18nRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/I18nRest.kt
new file mode 100644
index 0000000..f59f183
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/I18nRest.kt
@@ -0,0 +1,42 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.json.JsonUtils
+import de.micromata.borgbutler.server.I18nClientMessages
+import org.apache.commons.lang3.StringUtils
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import java.util.*
+import javax.servlet.http.HttpServletRequest
+
+@RestController
+@RequestMapping("/rest/i18n")
+class I18nRest {
+
+ /**
+ *
+ * @param request For detecting the user's client locale.
+ * @param locale If not given, the client's language (browser) will be used.
+ * @param keysOnly If true, only the keys will be returned. Default is false.
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @see JsonUtils.toJson
+ */
+ @GetMapping("list")
+ fun getList(
+ request: HttpServletRequest,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?,
+ @RequestParam("keysOnly", required = false) keysOnly: Boolean?,
+ @RequestParam("locale", required = false) locale: String?
+ ): String {
+ val localeObject: Locale?
+ if (StringUtils.isNotBlank(locale)) {
+ localeObject = Locale(locale)
+ } else {
+ localeObject = RestUtils.getUserLocale(request)
+ }
+ val translations: Map<String, String> =
+ I18nClientMessages.getInstance().getAllMessages(localeObject, keysOnly == true)
+ return JsonUtils.toJson(translations, prettyPrinter)
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/JobsRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/JobsRest.kt
new file mode 100644
index 0000000..f614f8b
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/JobsRest.kt
@@ -0,0 +1,201 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.BorgQueueExecutor
+import de.micromata.borgbutler.config.ConfigurationHandler
+import de.micromata.borgbutler.jobs.AbstractJob
+import de.micromata.borgbutler.json.JsonUtils
+import de.micromata.borgbutler.json.borg.ProgressInfo
+import de.micromata.borgbutler.server.rest.queue.JsonJob
+import de.micromata.borgbutler.server.rest.queue.JsonJobQueue
+import mu.KotlinLogging
+import org.apache.commons.collections4.CollectionUtils
+import org.apache.commons.lang3.StringUtils
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+
+private val log = KotlinLogging.logger {}
+
+@RestController
+@RequestMapping("/rest/jobs")
+class JobsRest {
+ /**
+ * @param repo If given, only the job queue of the given repo will be returned.
+ * @param testMode If true, then a test job list is created.
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @return Job queues as json string.
+ * @see JsonUtils.toJson
+ */
+ @GetMapping
+ fun getJobs(
+ @RequestParam("repo", required = false) repo: String?,
+ @RequestParam("testMode", required = false) testMode: Boolean?,
+ @RequestParam("oldJobs", required = false) oldJobs: Boolean?,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?
+ ): String {
+ log.debug("getJobs repo=$repo, oldJobs=$oldJobs")
+ if (testMode == true) {
+ // Return dynamic test queue:
+ return returnTestList(oldJobs, prettyPrinter)
+ }
+ var validRepo = false
+ if (StringUtils.isNotBlank(repo) && "null" != repo && "undefined" != repo) {
+ validRepo = true
+ }
+ val borgQueueExecutor = BorgQueueExecutor.getInstance()
+ val queueList = mutableListOf<JsonJobQueue>()
+ if (validRepo) { // Get only the queue of the given repo:
+ val queue = getQueue(repo, oldJobs)
+ if (queue != null) {
+ queueList.add(queue)
+ }
+ } else { // Get all the queues (of all repos).
+ for (rep in borgQueueExecutor.getRepos()) {
+ val queue = getQueue(rep, oldJobs)
+ if (queue != null) {
+ queueList.add(queue)
+ }
+ }
+ }
+ return JsonUtils.toJson(queueList, prettyPrinter)
+ }
+
+ private fun getQueue(repo: String?, oldJobs: Boolean?): JsonJobQueue? {
+ val borgQueueExecutor: BorgQueueExecutor = BorgQueueExecutor.getInstance()
+ val repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(repo) ?: return null
+ val borgJobList = borgQueueExecutor.getJobListCopy(repoConfig, oldJobs == true)
+ if (CollectionUtils.isEmpty(borgJobList)) return null
+ val queue: JsonJobQueue = JsonJobQueue().setRepo(repoConfig.getDisplayName())
+ queue.setJobs(mutableListOf())
+ for (borgJob in borgJobList) {
+ val job = JsonJob(borgJob)
+ queue.getJobs().add(job)
+ }
+ return queue
+ }
+
+ /**
+ * @param uniqueJobNumberString The id of the job to cancel.
+ */
+ @GetMapping("/cancel")
+ fun cancelJob(@RequestParam("uniqueJobNumber") uniqueJobNumberString: String) {
+ val uniqueJobNumber =
+ try {
+ uniqueJobNumberString.toLong()
+ } catch (ex: NumberFormatException) {
+ log.error("Can't cancel job, because unique job number couln't be parsed (long value expected): $uniqueJobNumberString")
+ return
+ }
+ BorgQueueExecutor.getInstance().cancelJob(uniqueJobNumber)
+ }
+
+ /**
+ * Only for test purposes and development.
+ *
+ * @param oldJobs
+ * @param prettyPrinter
+ * @return
+ */
+ private fun returnTestList(oldJobs: Boolean?, prettyPrinter: Boolean?): String {
+ var list = if (oldJobs == true) oldJobsTestList else testList
+ if (list == null) {
+ list = mutableListOf()
+ var uniqueJobNumber: Long = 100000
+ var queue: JsonJobQueue = JsonJobQueue().setRepo("My Computer")
+ addTestJob(queue, "info", "my-macbook", 0, 2342, uniqueJobNumber++, oldJobs == true)
+ addTestJob(queue, "list", "my-macbook", -1, -1, uniqueJobNumber++, oldJobs == true)
+ list.add(queue)
+ queue = JsonJobQueue().setRepo("My Server")
+ addTestJob(queue, "list", "my-server", 0, 1135821, uniqueJobNumber++, oldJobs == true)
+ addTestJob(queue, "info", "my-server", -1, -1, uniqueJobNumber++, oldJobs == true)
+ list.add(queue)
+ if (oldJobs == true) {
+ oldJobsTestList = list
+ } else {
+ testList = list
+ }
+ } else if (oldJobs != true) {
+ for (jobQueue in list) {
+ for (job in jobQueue.getJobs()) {
+ if (job.getStatus() != AbstractJob.Status.RUNNING) continue
+ var current: Long = job.getProgressInfo().getCurrent()
+ val total: Long = job.getProgressInfo().getTotal()
+ current += if (StringUtils.startsWith(job.getProgressInfo().getMessage(), "Calculating")) {
+ // Info is a faster operation:
+ (Math.random() * total / 5).toLong()
+ } else {
+ // than get the complete archive file list:
+ (Math.random() * total / 30).toLong()
+ }
+ if (current > total) {
+ current = 0 // Reset to beginning.
+ }
+ job.getProgressInfo().setCurrent(current)
+ if (job.getProgressText().startsWith("Calculating")) {
+ job.getProgressInfo()
+ .setMessage("Calculating statistics... " + Math.round((100 * current / total).toFloat()) + "%")
+ }
+ job.buildProgressText()
+ }
+ }
+ }
+ return JsonUtils.toJson(list, prettyPrinter)
+ }
+
+ /**
+ * Only for test purposes and development.
+ *
+ * @param queue
+ * @param operation
+ * @param host
+ * @param current
+ * @param total
+ * @return
+ */
+ private fun addTestJob(
+ queue: JsonJobQueue,
+ operation: String,
+ host: String,
+ current: Long,
+ total: Long,
+ uniqueNumber: Long,
+ oldJobs: Boolean
+ ): JsonJob {
+ val progressInfo = ProgressInfo()
+ .setCurrent(current)
+ .setTotal(total)
+ val job: JsonJob = JsonJob()
+ .setProgressInfo(progressInfo)
+ .setStatus(AbstractJob.Status.QUEUED)
+ if ("info" == operation) {
+ progressInfo.setMessage("Calculating statistics... ")
+ job.setDescription("Loading info of archive '" + host + "-2018-12-05T23:10:33' of repo '" + queue.getRepo() + "'.")
+ .setCommandLineAsString("borg info --json --log-json --progress ssh://...:23/./backups/$host::$host-2018-12-05T23:10:33")
+ } else {
+ progressInfo.setMessage("Getting file list... ")
+ job.setDescription("Loading list of files of archive '" + host + "-2018-12-05T17:30:38' of repo '" + queue.getRepo() + "'.")
+ .setCommandLineAsString("borg list --json-lines ssh://...:23/./backups/$host::$host-2018-12-05T17:30:38")
+ }
+ job.buildProgressText()
+ if (current >= 0) {
+ job.setStatus(AbstractJob.Status.RUNNING)
+ } else {
+ job.setStatus(AbstractJob.Status.QUEUED)
+ }
+ if (queue.getJobs() == null) {
+ queue.setJobs(ArrayList<JsonJob>())
+ }
+ job.setUniqueJobNumber(uniqueNumber)
+ if (oldJobs) {
+ job.setStatus(if (uniqueNumber % 2 == 0L) AbstractJob.Status.CANCELLED else AbstractJob.Status.DONE)
+ }
+ queue.getJobs().add(job)
+ return job
+ }
+
+ companion object {
+ private var testList: MutableList<JsonJobQueue>? = null
+ private var oldJobsTestList: MutableList<JsonJobQueue>? = null
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/LoggingRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/LoggingRest.kt
new file mode 100644
index 0000000..24036a3
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/LoggingRest.kt
@@ -0,0 +1,67 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.json.JsonUtils
+import de.micromata.borgbutler.server.logging.LoggerMemoryAppender
+import de.micromata.borgbutler.server.logging.LogFilter
+import de.micromata.borgbutler.server.logging.LogLevel
+import mu.KotlinLogging
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import javax.servlet.http.HttpServletRequest
+
+private val log = KotlinLogging.logger {}
+
+@RestController
+@RequestMapping("/rest/logging")
+class LoggingRest {
+ /**
+ * @param request
+ * @param search
+ * @param logLevelTreshold fatal, error, warn, info, debug or trace (case insensitive).
+ * @param maxSize Max size of the result list.
+ * @param ascendingOrder Default is false (default is descending order).
+ * @param lastReceivedOrderNumber The last received order number for updating log entries (preventing querying all entries again).
+ * @param prettyPrinter
+ * @return
+ */
+ @GetMapping("query")
+ fun query(
+ request: HttpServletRequest?,
+ @RequestParam("search", required = false) search: String?,
+ @RequestParam("treshold", required = false) logLevelTreshold: String?,
+ @RequestParam("maxSize", required = false) maxSize: Int?,
+ @RequestParam("ascendingOrder", required = false) ascendingOrder: Boolean?,
+ @RequestParam("lastReceivedOrderNumber", required = false) lastReceivedOrderNumber: Int?,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?
+ ): String {
+ val filter = LogFilter()
+ filter.setSearch(search)
+ if (logLevelTreshold != null) {
+ try {
+ val treshold =
+ LogLevel.valueOf(logLevelTreshold.trim { it <= ' ' }
+ .toUpperCase())
+ filter.setThreshold(treshold)
+ } catch (ex: IllegalArgumentException) {
+ log.error("Can't parse log level treshold: " + logLevelTreshold + ". Supported values (case insensitive): " + LogLevel.getSupportedValues())
+ }
+ }
+ if (filter.getThreshold() == null) {
+ filter.setThreshold(LogLevel.INFO)
+ }
+ if (maxSize != null) {
+ filter.setMaxSize(maxSize)
+ }
+ if (ascendingOrder != null && ascendingOrder == true) {
+ filter.setAscendingOrder(true)
+ }
+ if (lastReceivedOrderNumber != null) {
+ filter.setLastReceivedLogOrderNumber(lastReceivedOrderNumber)
+ }
+ val loggerMemoryAppender = LoggerMemoryAppender.getInstance()
+ return JsonUtils.toJson(loggerMemoryAppender.query(filter, RestUtils.getUserLocale(request!!)), prettyPrinter)
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ReposRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ReposRest.kt
new file mode 100644
index 0000000..eed277a
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/ReposRest.kt
@@ -0,0 +1,66 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.cache.ButlerCache
+import de.micromata.borgbutler.data.Repository
+import de.micromata.borgbutler.json.JsonUtils
+import mu.KotlinLogging
+import org.apache.commons.collections4.CollectionUtils
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+
+private val log = KotlinLogging.logger {}
+
+@RestController
+@RequestMapping("/rest/repos")
+class ReposRest {
+ /**
+ *
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @return A list of repositories of type [BorgRepository].
+ * @see JsonUtils.toJson
+ */
+ @GetMapping("list")
+ fun getList(@RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String {
+ val repositories: List<Repository?> = ButlerCache.getInstance().getAllRepositories()
+ return if (CollectionUtils.isEmpty(repositories)) {
+ "[]"
+ } else JsonUtils.toJson(repositories, prettyPrinter)
+ }
+
+ /**
+ *
+ * @param id id or name of repo.
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @return [Repository] (without list of archives) as json string.
+ * @see JsonUtils.toJson
+ */
+ @GetMapping("repo")
+ fun getRepo(@RequestParam("id") id: String,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?): String {
+ val repository: Repository = ButlerCache.getInstance().getRepository(id)
+ return JsonUtils.toJson(repository, prettyPrinter)
+ }
+
+ /**
+ *
+ * @param id id or name of repo.
+ * @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
+ */
+ @GetMapping("repoArchiveList")
+ fun getRepoArchiveList(
+ @RequestParam("id") id: String,
+ @RequestParam("force", required = false) force: Boolean?,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?
+ ): String {
+ if (force == true) {
+ val repo: Repository = ButlerCache.getInstance().getRepository(id)
+ ButlerCache.getInstance().clearRepoCacheAccess(repo)
+ }
+ val repository: Repository = ButlerCache.getInstance().getRepositoryArchives(id)
+ return JsonUtils.toJson(repository, prettyPrinter)
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RequestLog.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RequestLog.kt
new file mode 100644
index 0000000..4a2bb7f
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RequestLog.kt
@@ -0,0 +1,138 @@
+/////////////////////////////////////////////////////////////////////////////
+//
+// Project ProjectForge Community Edition
+// www.projectforge.org
+//
+// Copyright (C) 2001-2021 Micromata GmbH, Germany (www.micromata.com)
+//
+// ProjectForge is dual-licensed.
+//
+// This community edition is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as published
+// by the Free Software Foundation; version 3 of the License.
+//
+// This community edition is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+// Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, see http://www.gnu.org/licenses/.
+//
+/////////////////////////////////////////////////////////////////////////////
+
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.json.JsonUtils
+import java.security.Principal
+import java.util.*
+import javax.servlet.http.Cookie
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.Part
+
+/**
+ * Helper class for debugging requests. Converts a given request to json.
+ */
+object RequestLog {
+ @JvmStatic
+ fun asJson(request: HttpServletRequest, longForm: Boolean = false): String {
+ val data = RequestData(request, longForm)
+ return JsonUtils.toJson(data)
+ }
+
+ @JvmStatic
+ fun asString(request: HttpServletRequest): String {
+ return request.requestURI
+ }
+}
+
+class RequestData(request: HttpServletRequest, longForm: Boolean = false) {
+ val attributes = mutableMapOf<String, Any?>()
+ val parameters = mutableMapOf<String, String?>()
+ val headers = mutableMapOf<String, String?>()
+ var locales: MutableList<Locale>? = null
+ //val parts = mutableListOf<PartInfo>()
+
+ val authType: String? = request.authType
+ val characterEncoding: String? = request.characterEncoding
+ val contentLength: Int? = if (longForm) request.contentLength else null
+ val contentType: String? = if (longForm) request.contentType else null
+ val contextPath: String? = if (longForm) request.contextPath else null
+ val cookies: Array<Cookie>? = if (longForm) request.cookies else null
+ val isAsyncStarted: Boolean? = if (longForm) request.isAsyncStarted else null
+ val isRequestedSessionIdFromCookie: Boolean? = if (longForm) request.isRequestedSessionIdFromCookie else null
+ val isRequestedSessionIdFromURL: Boolean? = if (longForm) request.isRequestedSessionIdFromURL else null
+ val isRequestedSessionIdValid: Boolean? = if (longForm) request.isRequestedSessionIdValid else null
+ val isSecure: Boolean? = if (longForm) request.isSecure else null
+ val isTrailerFieldsReady: Boolean? = if (longForm) request.isTrailerFieldsReady else null
+ val localAddr: String? = if (longForm) request.localAddr else null
+ val localName: String? = if (longForm) request.localName else null
+ val localPort: Int? = if (longForm) request.localPort else null
+ val locale: Locale? = if (longForm) request.locale else null
+ val method: String? = request.method
+ val pathInfo: String? = if (longForm) request.pathInfo else null
+ val pathTranslated: String? = if (longForm) request.pathTranslated else null
+ val protocol: String? = if (longForm) request.protocol else null
+ val queryString: String? = request.queryString
+ val remoteAddr: String? = request.remoteAddr
+ val remoteHost: String? = if (longForm) request.remoteHost else null
+ val remotePort: Int? = if (longForm) request.remotePort else null
+ val remoteUser: String? = request.remoteUser
+ val requestedSessionId: String? = if (longForm) request.requestedSessionId else null
+ val requestUri: String? = request.requestURI
+ val scheme: String? = if (longForm) request.scheme else null
+ val serverName = if (longForm) request.serverName else null
+ val serverPort: Int? = if (longForm) request.serverPort else null
+ val servletPath: String? = if (longForm) request.servletPath else null
+ val sessionId: String? = request.session?.id
+ val userPrincipal: Principal? = request.userPrincipal
+
+
+ init {
+ for (attribute in request.attributeNames) {
+ attributes[attribute] = handleSecret(request, attribute, request.getAttribute(attribute)?.toString())
+ }
+ for (header in request.headerNames) {
+ headers[header] = handleSecret(request, header, request.getHeader(header)) as String
+ }
+ if (longForm) {
+ locales = mutableListOf()
+ locales?.let {
+ for (locale in request.locales) {
+ it.add(locale)
+ }
+ }
+ }
+ for (parameter in request.parameterNames) {
+ parameters[parameter] = handleSecret(request, parameter, request.getParameter(parameter)) as String
+ }
+ /*
+ for (part in request.parts) {
+ parts.add(PartInfo(part))
+ }*/
+ }
+
+ private fun <T> handleSecret(request: HttpServletRequest, name: String?, value: T?): Any? {
+ name ?: return null
+ value ?: return null
+ if (name.toLowerCase() == "authorization") {
+ return "authorization=******"
+ }
+ return value
+ }
+}
+
+class PartInfo(part: Part) {
+ val headers = mutableMapOf<String, String>()
+ // Can't get inputstream
+
+ val contentType: String? = part.contentType
+ val name: String? = part.name
+ val size = part.size
+
+ init {
+ for (header in part.headerNames) {
+ headers[header] = part.getHeader(header)
+ }
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt
new file mode 100644
index 0000000..9586f7d
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/RestUtils.kt
@@ -0,0 +1,112 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.server.RunningMode
+import de.micromata.borgbutler.server.user.UserData
+import de.micromata.borgbutler.server.user.UserUtils
+import org.slf4j.Logger
+import org.springframework.core.io.ByteArrayResource
+import org.springframework.core.io.InputStreamResource
+import org.springframework.core.io.Resource
+import org.springframework.http.HttpHeaders
+import org.springframework.http.MediaType
+import org.springframework.http.ResponseEntity
+import java.io.InputStream
+import java.net.InetAddress
+import java.net.UnknownHostException
+import java.util.*
+import javax.servlet.ServletRequest
+import javax.servlet.http.HttpServletRequest
+
+object RestUtils {
+ /**
+ * @return null, if the local app (JavaFX) is running and the request is from localhost. Otherwise message, why local
+ * service isn't available.
+ */
+ fun checkLocalDesktopAvailable(requestContext: HttpServletRequest): String? {
+ if (RunningMode.getServerType() != RunningMode.ServerType.DESKTOP) {
+ return "Service unavailable. No desktop app on localhost available."
+ }
+ val remoteAddr = requestContext.remoteAddr
+ return if (remoteAddr == null || remoteAddr != "127.0.0.1") {
+ "Service not available. Can't call this service remote. Run this service on localhost of the running desktop app."
+ } else null
+ }
+
+ /**
+ * @return Returns the user put by the UserFilter.
+ * @see UserUtils.getUser
+ * @see de.micromata.borgbutler.server.user.UserFilter
+ */
+ fun getUser(): UserData {
+ val user = UserUtils.getUser() ?: throw IllegalStateException("No user given in rest call.")
+ return UserUtils.getUser()
+ }
+
+ fun getUserLocale(requestContext: HttpServletRequest): Locale? {
+ val user = getUser()
+ var locale = user.locale
+ if (locale == null) {
+ locale = requestContext.locale
+ }
+ return locale
+ }
+
+ @JvmStatic
+ fun getClientIp(request: ServletRequest): String? {
+ var remoteAddr: String? = null
+ if (request is HttpServletRequest) {
+ remoteAddr = request.getHeader("X-Forwarded-For")
+ }
+ if (remoteAddr != null) {
+ if (remoteAddr.contains(",")) {
+ // sometimes the header is of form client ip,proxy 1 ip,proxy 2 ip,...,proxy n ip,
+ // we just want the client
+ remoteAddr = remoteAddr.split(',')[0].trim({ it <= ' ' })
+ }
+ try {
+ // If ip4/6 address string handed over, simply does pattern validation.
+ InetAddress.getByName(remoteAddr)
+ } catch (e: UnknownHostException) {
+ remoteAddr = request.remoteAddr
+ }
+
+ } else {
+ remoteAddr = request.remoteAddr
+ }
+ return remoteAddr
+ }
+
+ fun downloadFile(filename: String, inputStream: InputStream): ResponseEntity<InputStreamResource> {
+ return ResponseEntity.ok()
+ .contentType(MediaType.parseMediaType("application/octet-stream"))
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${filename.replace('"', '_')}\"")
+ .body(InputStreamResource(inputStream))
+ }
+
+ fun downloadFile(filename: String, content: String): ResponseEntity<String> {
+ return ResponseEntity.ok()
+ .contentType(MediaType.parseMediaType("application/octet-stream"))
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${filename.replace('"', '_')}\"")
+ .body(content)
+ }
+
+ fun downloadFile(filename: String, resource: ByteArrayResource): ResponseEntity<Resource> {
+ return ResponseEntity.ok()
+ .contentType(MediaType.parseMediaType("application/octet-stream"))
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"${filename.replace('"', '_')}\"")
+ .body(resource)
+ }
+
+ fun badRequest(message: String): ResponseEntity<String> {
+ return ResponseEntity.badRequest().body(message)
+ }
+
+ fun notFound(): ResponseEntity<String> {
+ return ResponseEntity.badRequest().body("Not found.")
+ }
+
+ fun notFound(log: Logger, errorMessage: String?): ResponseEntity<String> {
+ log.error(errorMessage)
+ return ResponseEntity.badRequest().body(errorMessage)
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfo.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfo.kt
new file mode 100644
index 0000000..d6a9d15
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfo.kt
@@ -0,0 +1,33 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.BorgQueueStatistics
+import de.micromata.borgbutler.server.BorgVersion
+
+/**
+ * Statistics of all the job queues, especially the number of total queued and running jobs.
+ * This is used e. g. by the client for showing a badge near to the menu entry "job monitor" with the number
+ * of Jobs in the queues.
+ */
+class SystemInfo {
+ var queueStatistics: BorgQueueStatistics? = null
+ private set
+ var isConfigurationOK = false
+ private set
+ var borgVersion: BorgVersion? = null
+ private set
+
+ fun setQueueStatistics(queueStatistics: BorgQueueStatistics?): SystemInfo {
+ this.queueStatistics = queueStatistics
+ return this
+ }
+
+ fun setConfigurationOK(configurationOK: Boolean): SystemInfo {
+ isConfigurationOK = configurationOK
+ return this
+ }
+
+ fun setBorgVersion(borgVersion: BorgVersion?): SystemInfo {
+ this.borgVersion = borgVersion
+ return this
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfoRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfoRest.kt
new file mode 100644
index 0000000..943cbb4
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/SystemInfoRest.kt
@@ -0,0 +1,26 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.BorgQueueExecutor
+import de.micromata.borgbutler.json.JsonUtils
+import de.micromata.borgbutler.server.BorgInstallation
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/rest/system")
+class SystemInfoRest {
+ /**
+ * @return The total number of jobs queued or running (and other statistics): [de.micromata.borgbutler.BorgQueueStatistics].
+ * @see JsonUtils.toJson
+ */
+ @GetMapping("info")
+ fun statistics(): SystemInfo {
+ val borgVersion = BorgInstallation.getInstance().getBorgVersion()
+ val systemInfonfo = SystemInfo()
+ .setQueueStatistics(BorgQueueExecutor.getInstance().getStatistics())
+ .setConfigurationOK(borgVersion.isVersionOK())
+ .setBorgVersion(borgVersion)
+ return systemInfonfo
+ }
+}
diff --git a/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/VersionRest.kt b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/VersionRest.kt
new file mode 100644
index 0000000..dfa3165
--- /dev/null
+++ b/borgbutler-server/src/main/kotlin/de/micromata/borgbutler/server/rest/VersionRest.kt
@@ -0,0 +1,66 @@
+package de.micromata.borgbutler.server.rest
+
+import de.micromata.borgbutler.json.JsonUtils
+import de.micromata.borgbutler.server.Languages
+import de.micromata.borgbutler.server.Version
+import org.apache.commons.lang3.StringUtils
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+import java.util.*
+import javax.servlet.http.HttpServletRequest
+
+@RestController
+@RequestMapping("/rest")
+class VersionRest {
+ /**
+ *
+ * @param request For detecting the user's client locale.
+ * @param prettyPrinter If true then the json output will be in pretty format.
+ * @see JsonUtils.toJson
+ */
+ @GetMapping("version")
+ fun getVersion(
+ request: HttpServletRequest,
+ @RequestParam("prettyPrinter", required = false) prettyPrinter: Boolean?
+ ): String {
+ val user = RestUtils.getUser()
+ var language = Languages.asString(user.getLocale())
+ if (StringUtils.isBlank(language)) {
+ val locale: Locale = request.locale
+ language = locale.getLanguage()
+ }
+ val version = MyVersion(language, RestUtils.checkLocalDesktopAvailable(request) == null)
+ return JsonUtils.toJson(version, prettyPrinter)
+ }
+
+ inner class MyVersion(language: String, localDesktopAvailable: Boolean) {
+ private val version: Version
+ val language: String
+ val isLocalDesktopAvailable: Boolean
+ val appName: String
+ get() = version.appName
+
+ fun getVersion(): String {
+ return version.version
+ }
+
+ val buildDateUTC: String
+ get() = version.buildDateUTC
+ val buildDate: Date
+ get() = version.buildDate
+
+ /**
+ * @return Version of the available update, if exist. Otherwise null.
+ */
+ val updateVersion: String?
+ get() = version.updateVersion
+
+ init {
+ version = Version.getInstance()
+ this.language = language
+ isLocalDesktopAvailable = localDesktopAvailable
+ }
+ }
+}
diff --git a/borgbutler-server/src/main/resources/application.properties b/borgbutler-server/src/main/resources/application.properties
new file mode 100644
index 0000000..0388a89
--- /dev/null
+++ b/borgbutler-server/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+server.address=127.0.0.1
+server.port=9042
diff --git a/borgbutler-server/src/main/resources/log4j.properties b/borgbutler-server/src/main/resources/log4j.properties
deleted file mode 100644
index ee7031b..0000000
--- a/borgbutler-server/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,21 +0,0 @@
-log4j.rootLogger=info, stdout, memory, file
-#log4j.logger.de.micromata.borgbutler.persistency=debug
-#log4j.logger.de.micromata.borgbutler.main.jetty=debug
-
-log4j.logger.org.apache.commons.jcs=WARN
-
-log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-
-# Pattern to output the caller's file name and line number.
-log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
-
-log4j.appender.memory=de.micromata.borgbutler.server.logging.Log4jMemoryAppender
-
-log4j.appender.file=org.apache.log4j.RollingFileAppender
-
-log4j.appender.file.File=${borgbutlerHome}borgbutler.log
-log4j.appender.file.MaxFileSize=10MB
-log4j.appender.file.MaxBackupIndex=5
-log4j.appender.file.layout=org.apache.log4j.PatternLayout
-log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
diff --git a/borgbutler-server/src/main/resources/logback-spring.xml b/borgbutler-server/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000..ddf1bbf
--- /dev/null
+++ b/borgbutler-server/src/main/resources/logback-spring.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+ <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+ <property name="LOG_HOME" value="${borgbutlerHome:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}}"/>
+ <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
+
+ <appender name="ROLLING-FILE-ALL"
+ class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <encoder>
+ <pattern>%d{MM-dd-yy HH:mm:ss} %-5level %mdc %logger{60}::%M:%line -
+ %msg%n
+ </pattern>
+ </encoder>
+ <file>${LOG_HOME}borgbutler.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
+ <fileNamePattern>${LOG_HOME}/borgbutler.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+ <minIndex>1</minIndex>
+ <maxIndex>10</maxIndex>
+ </rollingPolicy>
+ <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
+ <maxFileSize>10MB</maxFileSize>
+ </triggeringPolicy>
+ </appender>
+
+ <appender name="ROLLING-MEMORY" class="de.micromata.borgbutler.server.logging.LoggerMemoryAppender" />
+
+ <root level="INFO">
+ <appender-ref ref="CONSOLE"/>
+ <appender-ref ref="ROLLING-FILE-ALL"/>
+ <appender-ref ref="ROLLING-MEMORY"/>
+ </root>
+
+ <!-- custom logging levels -->
+ <!--logger name="de.micromata.merlin.excel" level="DEBUG" /-->
+</configuration>
diff --git a/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx b/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx
index cb960cc..97334b0 100644
--- a/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx
+++ b/borgbutler-webapp/src/components/views/archives/ArchiveView.jsx
@@ -25,7 +25,7 @@
fetch(getRestServiceUrl('archives', {
repo: this.state.repoId,
archiveId: this.state.archiveId,
- force: force
+ force: force === true
}), {
method: 'GET',
headers: {
diff --git a/borgbutler-webapp/src/components/views/archives/FileListPanel.jsx b/borgbutler-webapp/src/components/views/archives/FileListPanel.jsx
index b698f1a..5dd4a63 100644
--- a/borgbutler-webapp/src/components/views/archives/FileListPanel.jsx
+++ b/borgbutler-webapp/src/components/views/archives/FileListPanel.jsx
@@ -137,7 +137,7 @@
fetch(getRestServiceUrl('archives/filelist', {
archiveId: this.props.archive.id,
diffArchiveId: this.state.filter.diffArchiveId,
- force: force,
+ force: force === true,
searchString: this.state.filter.search,
mode: this.state.filter.mode,
currentDirectory: this.state.filter.currentDirectory,
diff --git a/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx b/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
index 6cc440b..1b5636c 100644
--- a/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
+++ b/borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
@@ -31,7 +31,7 @@
});
fetch(getRestServiceUrl('repos/repoArchiveList', {
id: this.state.id,
- force: force
+ force: force === true,
}), {
method: 'GET',
headers: {
diff --git a/build.gradle b/build.gradle
index 96b1dc8..5141f59 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,15 +6,20 @@
* user guide available at https://docs.gradle.org/5.0/userguide/tutorial_java_projects.html
*/
+plugins {
+ id "org.jetbrains.kotlin.jvm" version "1.4.32" apply false
+}
+
allprojects {
apply plugin: 'maven'
group = 'de.micromata.borgbutler'
- version = '0.4-SNAPSHOT'
+ version = '0.5-SNAPSHOT'
}
subprojects {
apply plugin: 'java'
+ apply plugin: 'org.jetbrains.kotlin.jvm'
sourceCompatibility = 1.9 // Needed: since 1.9 i18n properties in UTF-8 format.
targetCompatibility = 1.9
@@ -31,8 +36,6 @@
}
dependencies {
- compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25'
- testCompile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25'
testImplementation(
'org.junit.jupiter:junit-jupiter-api:5.3.0'
)
@@ -42,10 +45,6 @@
)
}
- subprojects {
- ext.depVersions = ['commonsio': '2.8.0', dep2: '2.0']
- }
-
test {
useJUnitPlatform()
}
diff --git a/doc/Development.adoc b/doc/Development.adoc
index c819664..3e2de5f 100644
--- a/doc/Development.adoc
+++ b/doc/Development.adoc
@@ -28,7 +28,7 @@
1. `cd borgbutler-webapp`
2. `npm install`
3. `gradle npmBuild` (builds the web archive)
-4. Start `de.micromata.borgbutler.server.Main`
+4. Start `BorgButlerApplication`
=== Start borgbutler-server for web development
For using hot code replacement of your web files, you should use `npm start` or `yarn start`:
@@ -36,7 +36,7 @@
1. `cd borgbutler-webapp`
2. `npm install`
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)
+4. Start `BorgButlerApplication` (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
--
Gitblit v1.10.0