From bbb9f7ce06a546768f662309bb635713aff70cb9 Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Mon, 8 Jun 2020 17:00:34 +0800 Subject: [PATCH] feat: automatic database backup closes #138 --- komga/Dockerfile | 1 + .../gotson/komga/application/tasks/Task.kt | 5 ++++ .../komga/application/tasks/TaskHandler.kt | 8 ++++- .../komga/application/tasks/TaskReceiver.kt | 4 +++ .../configuration/KomgaProperties.kt | 9 ++++++ .../infrastructure/h2/DatabaseBackuper.kt | 28 ++++++++++++++++++ .../PeriodicDatabaseBackupController.kt | 29 +++++++++++++++++++ komga/src/main/resources/application-dev.yml | 6 +++- komga/src/main/resources/application.yml | 3 ++ 9 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 komga/src/main/kotlin/org/gotson/komga/infrastructure/h2/DatabaseBackuper.kt create mode 100644 komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicDatabaseBackupController.kt diff --git a/komga/Dockerfile b/komga/Dockerfile index 4ac0c3e41..a52a58793 100644 --- a/komga/Dockerfile +++ b/komga/Dockerfile @@ -4,6 +4,7 @@ ARG DEPENDENCY=build/dependency COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY ${DEPENDENCY}/META-INF /app/META-INF COPY ${DEPENDENCY}/BOOT-INF/classes /app +ENV KOMGA_DATABASE_BACKUP_PATH="/config/database-backup.zip" ENV SPRING_DATASOURCE_URL="jdbc:h2:/config/database.h2" ENV SPRING_ARTEMIS_EMBEDDED_DATA_DIRECTORY="/config/artemis" ENV LOGGING_FILE_NAME="/config/logs/komga.log" diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt index 5ffa7eaf0..95a1d8782 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/Task.kt @@ -20,4 +20,9 @@ sealed class Task : Serializable { data class RefreshBookMetadata(val bookId: Long) : Task() { override fun uniqueId() = "REFRESH_BOOK_METADATA_$bookId" } + + object BackupDatabase : Task() { + override fun uniqueId(): String = "BACKUP_DATABASE" + override fun toString(): String = "BackupDatabase" + } } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt index 83ff80cc0..511069029 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskHandler.kt @@ -6,6 +6,7 @@ import org.gotson.komga.domain.persistence.LibraryRepository import org.gotson.komga.domain.service.BookLifecycle import org.gotson.komga.domain.service.LibraryScanner import org.gotson.komga.domain.service.MetadataLifecycle +import org.gotson.komga.infrastructure.h2.DatabaseBackuper import org.gotson.komga.infrastructure.jms.QUEUE_TASKS import org.gotson.komga.infrastructure.jms.QUEUE_TASKS_SELECTOR import org.springframework.jms.annotation.JmsListener @@ -21,7 +22,8 @@ class TaskHandler( private val bookRepository: BookRepository, private val libraryScanner: LibraryScanner, private val bookLifecycle: BookLifecycle, - private val metadataLifecycle: MetadataLifecycle + private val metadataLifecycle: MetadataLifecycle, + private val databaseBackuper: DatabaseBackuper ) { @JmsListener(destination = QUEUE_TASKS, selector = QUEUE_TASKS_SELECTOR) @@ -53,6 +55,10 @@ class TaskHandler( metadataLifecycle.refreshMetadata(it) } ?: logger.warn { "Cannot execute task $task: Book does not exist" } + is Task.BackupDatabase -> { + databaseBackuper.backupDatabase() + } + } }.also { logger.info { "Task $task executed in $it" } diff --git a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt index f8ee650e5..9141e57fc 100644 --- a/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt +++ b/komga/src/main/kotlin/org/gotson/komga/application/tasks/TaskReceiver.kt @@ -60,6 +60,10 @@ class TaskReceiver( submitTask(Task.RefreshBookMetadata(book.id)) } + fun databaseBackup() { + submitTask(Task.BackupDatabase) + } + private fun submitTask(task: Task) { logger.info { "Sending task: $task" } jmsTemplate.convertAndSend(QUEUE_TASKS, task) { diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt index 827d1c617..89b2705aa 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/configuration/KomgaProperties.kt @@ -27,4 +27,13 @@ class KomgaProperties { @Positive var validity: Int = 1209600 // 2 weeks } + + var databaseBackup = DatabaseBackup() + + class DatabaseBackup { + var enabled: Boolean = true + var path: String = "" + var schedule: String = "" + var startup: Boolean = true + } } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/h2/DatabaseBackuper.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/h2/DatabaseBackuper.kt new file mode 100644 index 000000000..101793bf0 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/h2/DatabaseBackuper.kt @@ -0,0 +1,28 @@ +package org.gotson.komga.infrastructure.h2 + +import mu.KotlinLogging +import org.gotson.komga.infrastructure.configuration.KomgaProperties +import org.springframework.jdbc.core.JdbcTemplate +import org.springframework.stereotype.Component +import java.nio.file.Files +import java.nio.file.Paths + +private val logger = KotlinLogging.logger {} + +@Component +class DatabaseBackuper( + private val jdbcTemplate: JdbcTemplate, + private val komgaProperties: KomgaProperties +) { + + fun backupDatabase() { + val path = Paths.get(komgaProperties.databaseBackup.path) + + Files.deleteIfExists(path) + + val command = "BACKUP TO '$path'" + + logger.info { "Executing command: $command" } + jdbcTemplate.execute(command) + } +} diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicDatabaseBackupController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicDatabaseBackupController.kt new file mode 100644 index 000000000..151bfde99 --- /dev/null +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/scheduler/PeriodicDatabaseBackupController.kt @@ -0,0 +1,29 @@ +package org.gotson.komga.interfaces.scheduler + +import mu.KotlinLogging +import org.gotson.komga.application.tasks.TaskReceiver +import org.gotson.komga.infrastructure.configuration.KomgaProperties +import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.context.annotation.Profile +import org.springframework.context.event.EventListener +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Controller + +private val logger = KotlinLogging.logger {} + +@Profile("!test") +@Controller +class PeriodicDatabaseBackupController( + private val taskReceiver: TaskReceiver, + private val komgaProperties: KomgaProperties +) { + + @EventListener(classes = [ApplicationReadyEvent::class], condition = "@komgaProperties.databaseBackup.startup") + @Scheduled(cron = "#{@komgaProperties.databaseBackup.schedule ?: '-'}") + fun scanAllLibraries() { + if (komgaProperties.databaseBackup.enabled) { + logger.info { "Periodic database backup" } + taskReceiver.databaseBackup() + } + } +} diff --git a/komga/src/main/resources/application-dev.yml b/komga/src/main/resources/application-dev.yml index b7cf93f33..a6dbb30b7 100644 --- a/komga/src/main/resources/application-dev.yml +++ b/komga/src/main/resources/application-dev.yml @@ -3,9 +3,13 @@ komga: remember-me: key: changeMe! validity: 2592000 # 1 month -# libraries-scan-cron: "*/5 * * * * ?" #every 5 seconds + # libraries-scan-cron: "*/5 * * * * ?" #every 5 seconds libraries-scan-cron: "-" #disable libraries-scan-startup: true + database-backup: + enabled: false + path: ./backup.zip + schedule: "*/15 * * * * ?" spring: datasource: url: jdbc:h2:mem:testdb diff --git a/komga/src/main/resources/application.yml b/komga/src/main/resources/application.yml index 4e2c16c43..9f1b90078 100644 --- a/komga/src/main/resources/application.yml +++ b/komga/src/main/resources/application.yml @@ -12,6 +12,9 @@ komga: libraries-scan-directory-exclusions: - "#recycle" - "@eaDir" + database-backup: + path: ~/.komga/database-backup.zip + schedule: "0 0 */6 * * ?" spring: # cache: