From 07a619f826f665a51cd2a6e454201be10f487835 Mon Sep 17 00:00:00 2001 From: Lars Westermann Date: Wed, 12 Jun 2019 20:54:21 +0200 Subject: [PATCH] Add backup --- .../resources/style/components/_board.scss | 1 + .../kotlin/de/kif/backend/Application.kt | 3 + .../kotlin/de/kif/backend/Configuration.kt | 6 ++ .../kotlin/de/kif/backend/Resources.kt | 8 +++ .../kotlin/de/kif/backend/util/Backup.kt | 65 ++++++++++++++++--- src/jvmMain/resources/portal.toml | 2 + 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/jsMain/resources/style/components/_board.scss b/src/jsMain/resources/style/components/_board.scss index 678b2bf..08e3eb7 100644 --- a/src/jsMain/resources/style/components/_board.scss +++ b/src/jsMain/resources/style/components/_board.scss @@ -85,6 +85,7 @@ .board-running { display: flex; flex-wrap: wrap; + align-content: flex-start; &:empty + .board-running-empty { display: block; diff --git a/src/jvmMain/kotlin/de/kif/backend/Application.kt b/src/jvmMain/kotlin/de/kif/backend/Application.kt index f0ae513..0d05e06 100644 --- a/src/jvmMain/kotlin/de/kif/backend/Application.kt +++ b/src/jvmMain/kotlin/de/kif/backend/Application.kt @@ -3,6 +3,7 @@ package de.kif.backend import com.fasterxml.jackson.databind.SerializationFeature import de.kif.backend.route.* import de.kif.backend.route.api.* +import de.kif.backend.util.Backup import de.kif.backend.util.pushService import io.ktor.application.Application import io.ktor.application.call @@ -101,4 +102,6 @@ fun Application.main() { } logger.info { "Responding at http://${Configuration.Server.host}:${Configuration.Server.port}$prefix/" } + + Backup.startBackupService() } diff --git a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt index 688a46b..038f8b1 100644 --- a/src/jvmMain/kotlin/de/kif/backend/Configuration.kt +++ b/src/jvmMain/kotlin/de/kif/backend/Configuration.kt @@ -43,6 +43,7 @@ object Configuration { val uploads by required() val database by required() val announcement by required() + val backup by required() } object Path { @@ -60,6 +61,9 @@ object Configuration { val announcement by c(PathSpec.announcement) val announcementPath: java.nio.file.Path by lazy { Paths.get(announcement).toAbsolutePath() } + + val backup by c(PathSpec.backup) + val backupPath: java.nio.file.Path by lazy { Paths.get(backup).toAbsolutePath() } } private object ScheduleSpec : ConfigSpec("schedule") { @@ -93,6 +97,7 @@ object Configuration { private object GeneralSpec : ConfigSpec("general") { val allowedUploadExtensions by required("allowed_upload_extensions") val wikiUrl by required("wiki_url") + val backupInterval by required("backup_interval") } object General { @@ -101,6 +106,7 @@ object Configuration { allowedUploadExtensions.split(",").map { it.trim().toLowerCase() }.toSet() } val wikiUrl by c(GeneralSpec.wikiUrl) + val backupInterval by c(GeneralSpec.backupInterval) } private object TwitterSpec : ConfigSpec("twitter") { diff --git a/src/jvmMain/kotlin/de/kif/backend/Resources.kt b/src/jvmMain/kotlin/de/kif/backend/Resources.kt index 0e4dc63..07d4a8e 100644 --- a/src/jvmMain/kotlin/de/kif/backend/Resources.kt +++ b/src/jvmMain/kotlin/de/kif/backend/Resources.kt @@ -83,11 +83,19 @@ object Resources { Files.createDirectories(Configuration.Path.sessionsPath) Files.createDirectories(Configuration.Path.uploadsPath) Files.createDirectories(Configuration.Path.webPath) + Files.createDirectories(Configuration.Path.backupPath) + + if (!Files.exists(Configuration.Path.announcementPath)) { + Files.createFile(Configuration.Path.announcementPath) + } logger.info { "Database path: ${Configuration.Path.databasePath}" } logger.info { "Sessions path: ${Configuration.Path.sessionsPath}" } logger.info { "Uploads path: ${Configuration.Path.uploadsPath}" } logger.info { "Web path: ${Configuration.Path.webPath}" } + logger.info { "Backup path: ${Configuration.Path.backupPath}" } + + logger.info { "Announcement file: ${Configuration.Path.announcementPath}" } logger.info { "Extract web content..." } extractWeb() diff --git a/src/jvmMain/kotlin/de/kif/backend/util/Backup.kt b/src/jvmMain/kotlin/de/kif/backend/util/Backup.kt index ae33de4..d6ee823 100644 --- a/src/jvmMain/kotlin/de/kif/backend/util/Backup.kt +++ b/src/jvmMain/kotlin/de/kif/backend/util/Backup.kt @@ -1,11 +1,18 @@ package de.kif.backend.util +import de.kif.backend.Configuration import de.kif.backend.database.Connection import de.kif.backend.repository.* import de.kif.common.RepositoryType import de.kif.common.Serialization import de.kif.common.model.* +import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable +import mu.KotlinLogging +import java.lang.Exception +import java.text.SimpleDateFormat +import java.util.* +import kotlin.concurrent.thread @Serializable data class Backup( @@ -17,6 +24,8 @@ data class Backup( val workGroups: List = emptyList() ) { companion object { + private val logger = KotlinLogging.logger {} + suspend fun backup(vararg repositories: RepositoryType): String { var backup = Backup() @@ -78,7 +87,9 @@ data class Backup( var workGroup = it val track = workGroup.track if (track != null) { - workGroup = workGroup.copy(track = track.copy(id = trackMap.firstOrNull { (i,_) -> i.equalsIgnoreId(track) }?.second ?: run { + workGroup = workGroup.copy(track = track.copy(id = trackMap.firstOrNull { (i, _) -> + i.equalsIgnoreId(track) + }?.second ?: run { println("Cannot import work group, due to missing track") return@mapNotNull null })) @@ -94,14 +105,16 @@ data class Backup( newSchedules.forEach { ScheduleRepository.create( it.copy( - room = it.room.copy(id = roomMap.firstOrNull { (i,_) -> i.equalsIgnoreId(it.room) }?.second ?: run { - println("Cannot import schedule, due to missing room") - return@forEach - }), - workGroup = it.workGroup.copy(id = workGroupMap.firstOrNull { (i,_) -> i.equalsIgnoreId(it.workGroup) }?.second ?: run { - println("Cannot import schedule, due to missing work group") - return@forEach - }) + room = it.room.copy( + id = roomMap.firstOrNull { (i, _) -> i.equalsIgnoreId(it.room) }?.second ?: run { + println("Cannot import schedule, due to missing room") + return@forEach + }), + workGroup = it.workGroup.copy( + id = workGroupMap.firstOrNull { (i, _) -> i.equalsIgnoreId(it.workGroup) }?.second ?: run { + println("Cannot import schedule, due to missing work group") + return@forEach + }) ) ) @@ -114,5 +127,39 @@ data class Backup( import(data) } + + fun startBackupService() { + val backupPath = Configuration.Path.backupPath.toFile() + val backupInterval = Configuration.General.backupInterval + + val formatter = SimpleDateFormat("yyyyMMdd'T'HHmmss") + + thread( + start = true, + isDaemon = true, + name = "backup-service" + ) { + while (true) { + try { + Thread.sleep(backupInterval) + + if (!backupPath.exists()) { + backupPath.mkdirs() + } + + val date = formatter.format(Date()) + + val backupFile = backupPath.resolve("backup_$date.json") + + val backup = runBlocking { + backup(*RepositoryType.values()) + } + backupFile.writeText(backup) + } catch (e: Exception) { + logger.error(e) { "Cannot create backup" } + } + } + } + } } } diff --git a/src/jvmMain/resources/portal.toml b/src/jvmMain/resources/portal.toml index e92d2c3..809ffa1 100644 --- a/src/jvmMain/resources/portal.toml +++ b/src/jvmMain/resources/portal.toml @@ -10,6 +10,7 @@ sessions = "data/sessions" uploads = "data/uploads" database = "data/portal.db" announcement = "data/announcement.txt" +backup = "data/backup" [schedule] reference = "1970-01-01" @@ -27,6 +28,7 @@ sign_key = "d1 20 23 8c 01 f8 f0 0d 9d 7c ff 68 21 97 75 31 38 3f fb 91 20 3a 8d [general] allowed_upload_extensions = "png, jpg, jpeg" wiki_url = "" +backup_interval = 3600000 [twitter] timeline = ""