diff --git a/pom.xml b/pom.xml
index 458b4b65..f9ad7892 100644
--- a/pom.xml
+++ b/pom.xml
@@ -201,6 +201,15 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.10.1
+
+ 9
+ 9
+
+
diff --git a/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt b/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt
index aa7967bd..f09aa6fb 100644
--- a/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt
+++ b/src/main/kotlin/com/ex_dock/ex_dock/database/JDBCStarter.kt
@@ -7,6 +7,7 @@ import com.ex_dock.ex_dock.database.home.HomeJdbcVerticle
import com.ex_dock.ex_dock.database.product.*
import com.ex_dock.ex_dock.database.scope.ScopeJdbcVerticle
import com.ex_dock.ex_dock.database.server.ServerJDBCVerticle
+import com.ex_dock.ex_dock.database.service.DatabaseBackupVerticle
import com.ex_dock.ex_dock.database.text_pages.TextPagesJdbcVerticle
import com.ex_dock.ex_dock.database.url.UrlJdbcVerticle
import com.ex_dock.ex_dock.helper.deployWorkerVerticleHelper
@@ -24,7 +25,7 @@ class JDBCStarter: AbstractVerticle() {
Future.all(verticles)
.onComplete {
println("All JDBC verticles deployed")
- TODO("Add all codecs to the eventbus")
+// TODO("Add all codecs to the eventbus")
}
.onFailure { error ->
println("Failed to deploy JDBC verticles: $error")
@@ -47,6 +48,7 @@ class JDBCStarter: AbstractVerticle() {
verticles.add(deployWorkerVerticleHelper(vertx, ProductStoreViewEavJdbcVerticle::class.qualifiedName.toString(), 5, 5))
verticles.add(deployWorkerVerticleHelper(vertx, ProductWebsiteEavJdbcVerticle::class.qualifiedName.toString(), 5, 5))
verticles.add(deployWorkerVerticleHelper(vertx, ProductCustomAttributesJdbcVerticle::class.qualifiedName.toString(), 5, 5))
+ verticles.add(deployWorkerVerticleHelper(vertx, DatabaseBackupVerticle::class.qualifiedName.toString(), 1, 1))
}
}
diff --git a/src/main/kotlin/com/ex_dock/ex_dock/database/service/DatabaseBackupVerticle.kt b/src/main/kotlin/com/ex_dock/ex_dock/database/service/DatabaseBackupVerticle.kt
new file mode 100644
index 00000000..53210ee1
--- /dev/null
+++ b/src/main/kotlin/com/ex_dock/ex_dock/database/service/DatabaseBackupVerticle.kt
@@ -0,0 +1,254 @@
+package com.ex_dock.ex_dock.database.service
+
+import com.ex_dock.ex_dock.database.connection.Connection
+import io.vertx.core.AbstractVerticle
+import io.vertx.core.eventbus.EventBus
+import io.vertx.sqlclient.Pool
+import java.io.BufferedReader
+import java.io.File
+import java.io.IOException
+import java.io.InputStreamReader
+import java.time.LocalTime
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.format.DateTimeFormatter
+import java.time.temporal.ChronoField
+import java.time.temporal.ChronoUnit
+import java.util.*
+
+/**
+ * Verticle for all events connected to database backups
+ */
+class DatabaseBackupVerticle: AbstractVerticle() {
+ private lateinit var client: Pool
+ private lateinit var eventBus: EventBus
+ private lateinit var frequency: String
+ private lateinit var time: String
+ private lateinit var dbUser: String
+ private lateinit var dbPassword: String
+ private var savedBackups = 1
+
+ /**
+ * Initialize variables from properties and start the eventbus and scheduler
+ */
+ override fun start() {
+ client = Connection().getConnection(vertx)
+ eventBus = vertx.eventBus()
+
+ //Read the variables from the properties file
+ try {
+ val props: Properties = javaClass.classLoader.getResourceAsStream("secret.properties").use {
+ Properties().apply { load(it) }
+ }
+
+ frequency = props.getProperty("BACKUP_FREQUENCY")
+ time = props.getProperty("BACKUP_TIME")
+ savedBackups = props.getProperty("SAVED_BACKUPS").toInt()
+ dbUser = props.getProperty("DATABASE_USERNAME")
+ dbPassword = props.getProperty("DATABASE_PASSWORD")
+
+ } catch (e: Exception) {
+ println(e.message)
+ try {
+ // Try to load the docker values when run in GitHub actions
+ val isDocker: Boolean = !System.getenv("GITHUB_RUN_NUMBER").isNullOrEmpty()
+ if (isDocker) {
+ frequency = "daily"
+ time = "00:00"
+ savedBackups = 1
+ } else {
+ error("Could not load the Properties file!")
+ }
+ } catch (e: Exception) {
+ println(e.message)
+ error("Could not read the Properties file!")
+ }
+ }
+
+ backupWithEventBusRequest()
+ scheduler()
+ }
+
+ /**
+ * Makes a full backup of the current state of the database
+ */
+ private fun backupDatabaseData() {
+ organizeBackups()
+ val zdt: ZonedDateTime = ZonedDateTime.now(ZoneId.systemDefault())
+ val zdtTruncatedHour: ZonedDateTime = zdt.truncatedTo(ChronoUnit.HOURS)
+ val f: DateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy HH", Locale.getDefault())
+ val timeString = zdtTruncatedHour.format(f)
+ .replace(" ", "_")
+ .replace("/", "_") +
+ ".sql"
+
+ val backupDirectory = System.getProperty("user.dir") + "\\database\\backup\\backup_$timeString"
+ val processBuilder = ProcessBuilder(
+ "pg_dump",
+ "--host", "localhost",
+ "--port", "8890",
+ "--file", backupDirectory,
+ "--blobs",
+ "--verbose",
+ "--username", dbUser,
+ "--no-password",
+ "ex-dock"
+ )
+
+ try {
+ val env = processBuilder.environment()
+ env["PGPASSWORD"] = dbPassword
+ val p: Process = processBuilder.start()
+ val bufferedReader = BufferedReader(InputStreamReader(p.errorStream))
+ var line = bufferedReader.readLine()
+ while (line!= null) {
+ println(line)
+ line = bufferedReader.readLine()
+ }
+ } catch (e: IOException) {
+ e.printStackTrace()
+ }
+ }
+
+ /**
+ * Gets the request for a backup from the eventbus
+ */
+ private fun backupWithEventBusRequest() {
+ eventBus.consumer("process.service.backup").handler { message ->
+ backupDatabaseData()
+ message.reply("Completed backup!")
+ }
+ }
+
+ /**
+ * Creates a scheduler for making backups at specified intervals
+ */
+ private fun scheduler() {
+ val frequencyInt: Long = getFrequencyTimeNumber(frequency)
+ val timeInt: Long = getTimeNumber(time)
+ val currentTime: Long = LocalTime.now(ZoneId.systemDefault())[ChronoField.MILLI_OF_DAY].toLong()
+ var waitingTime: Long = timeInt - currentTime
+
+ if (waitingTime < 0) {
+ val tempTimeInt = getTimeNumber("23:59")
+ waitingTime = (tempTimeInt - currentTime) + timeInt
+ }
+
+ vertx.setTimer(waitingTime) {
+ backupDatabaseData()
+
+ // Sets the given interval
+ vertx.setPeriodic(frequencyInt*1000L) {
+ backupDatabaseData()
+ }
+ }
+ }
+
+ /**
+ * Converts the given frequency to a usable number for the scheduler
+ */
+ private fun getFrequencyTimeNumber(frequency: String): Long {
+ return when (frequency) {
+ "daily" -> 1*24*60*60
+ "weekly" -> 7*24*60*60
+ "monthly" -> 30*24*60*60
+ "hourly" -> 60*60
+ else -> 1
+ }
+ }
+
+ /**
+ * Gets the time in milliseconds for the wanted backup time
+ */
+ private fun getTimeNumber(time: String): Long {
+ val parts = time.split(":")
+ return (parts[0].toInt() * 60 * 60 + parts[1].toInt() * 60) * 1000L
+ }
+
+ /**
+ * Removes the oldest backup if the number of backups is greater than the maximum number of backups allowed
+ */
+ private fun organizeBackups() {
+ val backupDirectory = System.getProperty("user.dir") + "\\database\\backup"
+ val folder = File(backupDirectory)
+ val listOfFiles = folder.listFiles()
+ val fileDateList: MutableList = emptyList().toMutableList()
+ var oldestDate = ""
+
+ // Skip organizing when the number of backups is lower than the maximum number of backups allowed
+ if (listOfFiles!!.size >= savedBackups) {
+ for (f in listOfFiles) {
+ val fileDateString = f.name
+ .replace("backup_", "")
+ .replace(".sql", "")
+ fileDateList.add(fileDateString)
+ }
+
+ for (dateString in fileDateList) {
+ oldestDate = if (oldestDate != "") {
+ val dateParts = dateString.split("_")
+ val oldestDateParts = oldestDate.split("_")
+
+ checkIfOlderFile(
+ dateParts,
+ oldestDateParts,
+ dateString,
+ oldestDate)
+ } else {
+ dateString
+ }
+ }
+
+ val oldestBackup = File("$backupDirectory\\backup_$oldestDate.sql")
+
+ if (oldestBackup.delete()) {
+ println("Deleted oldest backup from: $oldestDate")
+ } else {
+ println("Could not delete oldest backup from: $oldestDate")
+ }
+ }
+ }
+
+ /**
+ * Checks the backup name and returns the oldest backup date
+ */
+ private fun checkIfOlderFile(dateParts: List,
+ oldestDateParts: List,
+ dateString: String,
+ oldestDate: String): String {
+ // Check if the Year is older
+ if (dateParts[2].toInt() < oldestDateParts[2].toInt()) {
+ return dateString
+
+ // Check if the Year is the same
+ } else if (dateParts[2].toInt() == oldestDateParts[2].toInt()) {
+
+ // Check if the Month is older
+ if (dateParts[1].toInt() < oldestDateParts[1].toInt()) {
+ return dateString
+
+ // Check if the Month is the same
+ } else if (dateParts[1].toInt() == oldestDateParts[1].toInt()) {
+
+ // Check if the Day is older
+ if (dateParts[0].toInt() < oldestDateParts[0].toInt()) {
+ return dateString
+
+ // Check if the Day is the same
+ } else if (dateParts[0].toInt() == oldestDateParts[0].toInt()) {
+
+ // Check if the Hour is older
+ if (dateParts[3].toInt() < oldestDateParts[3].toInt()) {
+ return dateString
+
+ // Check if the Hour is the same
+ } else if (dateParts[3].toInt() == oldestDateParts[3].toInt()) {
+ return dateString
+ }
+ }
+ }
+ }
+
+ return oldestDate
+ }
+}
diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties
index 2366c18a..659d9bd0 100644
--- a/src/main/resources/default.properties
+++ b/src/main/resources/default.properties
@@ -6,3 +6,6 @@ JDBC_URL=127.0.0.1
DATABASE_URL="test_url"
DATABASE_PASSWORD="test_password"
DATABASE_USERNAME=test_username
+BACKUP_FREQUENCY="daily"
+BACKUP_TIME="23:00"
+SAVED_BACKUPS=1