Release 0.5 started: SpringBoot instead of Jetty, Jersey etc.
19 files deleted
23 files added
17 files modified
| | |
| | | plugins { |
| | | id 'java' |
| | | id "org.jetbrains.kotlin.jvm" version "1.4.32" |
| | | } |
| | | |
| | | description = 'borgbutler-core' |
| | |
| | | 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' |
| | | |
| | |
| | | } |
| | | |
| | | sourceSets { |
| | | main.kotlin.srcDirs += 'src/main/kotlin' |
| | | main.java.srcDirs += 'src/main/java' |
| | | } |
| | | |
| | |
| | | minHeapSize = "128m" |
| | | maxHeapSize = "1500m" |
| | | } |
| | | compileKotlin { |
| | | kotlinOptions { |
| | | jvmTarget = "1.8" |
| | | } |
| | | } |
| | | compileTestKotlin { |
| | | kotlinOptions { |
| | | jvmTarget = "1.9" |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | public void setId(String id) { |
| | | this.id = id; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return JsonUtils.toJson(this); |
| | | } |
| | | } |
| | |
| | | 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 |
| | | |
| | |
| | | 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 |
| | |
| | | } |
| | | |
| | | @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 |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | |
| | | 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 |
| | |
| | | |
| | | 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 |
| | |
| | | |
| | | plugins { |
| | | id 'java' |
| | | id 'org.springframework.boot' version '2.4.5' |
| | | } |
| | | |
| | | description = 'borgbutler-server' |
| | |
| | | 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' |
| | |
| | | |
| | | apply plugin: 'application' |
| | | apply plugin: 'kotlin' |
| | | mainClassName = "de.micromata.borgbutler.server.Main" |
| | | mainClassName = "de.micromata.borgbutler.server.BorgButlerApplication" |
| | | |
| | | run() { |
| | | doFirst { |
| | |
| | | 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; |
| | |
| | | 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: |
| | |
| | | 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; |
| | |
| | | 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) { |
| | |
| | | return javaClassSimpleName; |
| | | } |
| | | |
| | | public String getLineNumber() { |
| | | public int getLineNumber() { |
| | | return lineNumber; |
| | | } |
| | | |
| | |
| | | String dateFormat = userData.getDateFormat(); |
| | | this.singleUser.setDateFormat(dateFormat); |
| | | String lang = Languages.asString(locale); |
| | | if (lang != null) { |
| | | preferences.put(USER_LOCAL_PREF_KEY, lang); |
| | | } else { |
| | | preferences.remove(USER_LOCAL_PREF_KEY); |
| | | } |
| | | try { |
| | | preferences.flush(); |
| | | } catch (BackingStoreException ex) { |
| | |
| | | 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; |
| | | |
| | | /** |
| | |
| | | * <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); |
| | | |
| | |
| | | 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(); |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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("/**") |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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); |
| | | } |
| | | } |
| | | }*/ |
| | | } |
| | | } |
| New file |
| | |
| | | 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() |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | ) |
| New file |
| | |
| | | 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" |
| | | } |
| | | } |
| New file |
| | |
| | | ///////////////////////////////////////////////////////////////////////////// |
| | | // |
| | | // 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") |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | ///////////////////////////////////////////////////////////////////////////// |
| | | // |
| | | // 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) |
| | | } |
| | | } |
| New file |
| | |
| | | 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) |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | 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) |
| | | } |
| | | } |
| New file |
| | |
| | | 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) |
| | | } |
| | | } |
| New file |
| | |
| | | ///////////////////////////////////////////////////////////////////////////// |
| | | // |
| | | // 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) |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | 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) |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| New file |
| | |
| | | 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 |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | server.address=127.0.0.1 |
| | | server.port=9042 |
| New file |
| | |
| | | <?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> |
| | |
| | | fetch(getRestServiceUrl('archives', { |
| | | repo: this.state.repoId, |
| | | archiveId: this.state.archiveId, |
| | | force: force |
| | | force: force === true |
| | | }), { |
| | | method: 'GET', |
| | | headers: { |
| | |
| | | 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, |
| | |
| | | }); |
| | | fetch(getRestServiceUrl('repos/repoArchiveList', { |
| | | id: this.state.id, |
| | | force: force |
| | | force: force === true, |
| | | }), { |
| | | method: 'GET', |
| | | headers: { |
| | |
| | | * 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 |
| | | |
| | |
| | | } |
| | | |
| | | 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' |
| | | ) |
| | |
| | | ) |
| | | } |
| | | |
| | | subprojects { |
| | | ext.depVersions = ['commonsio': '2.8.0', dep2: '2.0'] |
| | | } |
| | | |
| | | test { |
| | | useJUnitPlatform() |
| | | } |
| | |
| | | 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`: |
| | |
| | | 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 |