|
1 | 1 | package com.mairwunnx.projectessentials.backup.managers |
2 | 2 |
|
| 3 | +import com.mairwunnx.projectessentials.backup.configurations.BackupConfiguration |
| 4 | +import com.mairwunnx.projectessentials.core.api.v1.configuration.ConfigurationAPI.getConfigurationByName |
| 5 | +import com.mairwunnx.projectessentials.core.api.v1.extensions.empty |
| 6 | +import kotlinx.coroutines.* |
| 7 | +import net.minecraftforge.api.distmarker.Dist |
| 8 | +import net.minecraftforge.fml.DistExecutor |
| 9 | +import net.minecraftforge.fml.server.ServerLifecycleHooks.getCurrentServer |
| 10 | +import org.apache.logging.log4j.LogManager |
| 11 | +import org.apache.logging.log4j.MarkerManager |
| 12 | +import org.zeroturnaround.zip.ZipUtil |
| 13 | +import java.io.File |
| 14 | +import java.text.SimpleDateFormat |
| 15 | +import java.util.* |
| 16 | +import kotlin.system.measureTimeMillis |
| 17 | + |
3 | 18 | object BackupManager { |
| 19 | + private val logger = LogManager.getLogger() |
| 20 | + private val marker = MarkerManager.getMarker("BACKUP") |
| 21 | + private var firstLaunch = true |
| 22 | + private lateinit var job: Job |
| 23 | + |
| 24 | + private val backupConfiguration by lazy { |
| 25 | + getConfigurationByName<BackupConfiguration>("backup").take() |
| 26 | + } |
| 27 | + |
4 | 28 | fun initialize() { |
| 29 | + if (backupConfiguration.backupEnabled) { |
| 30 | + logger.debug(marker, "Initializing backup loop") |
| 31 | + job = CoroutineScope(Dispatchers.Default).launch { |
| 32 | + while (isActive) loop() |
| 33 | + }.also { it.start() } |
| 34 | + } |
| 35 | + } |
| 36 | + |
| 37 | + fun terminate() = logger.debug(marker, "Terminating backup loop").also { |
| 38 | + job.cancel().also { firstLaunch = true } |
| 39 | + } |
| 40 | + |
| 41 | + private suspend fun loop() { |
| 42 | + if (backupConfiguration.backupEnabled) { |
| 43 | + if (backupConfiguration.firstLaunchDelay) { |
| 44 | + if (firstLaunch) { |
| 45 | + logger.debug(marker, "Backup loop do first launch delay") |
| 46 | + delay(backupConfiguration.backupCreationDelay * 1000L) |
| 47 | + firstLaunch = false |
| 48 | + } |
| 49 | + } |
5 | 50 |
|
| 51 | + File(backupConfiguration.backupDirectoryPath).also { it.mkdirs() }.let { |
| 52 | + purge(it).run { rotate(it) }.run { compile(it) } |
| 53 | + } |
| 54 | + } else { |
| 55 | + logger.debug(marker, "Backup loop was aborted with configuration").also { terminate() } |
| 56 | + } |
6 | 57 | } |
7 | 58 |
|
8 | | - fun terminate() { |
| 59 | + private fun purge(out: File) { |
| 60 | + if (backupConfiguration.purgeBackupOutDirectory) { |
| 61 | + logger.debug(marker, "Purging backup out directory") |
| 62 | + // @formatter:off |
| 63 | + out.listFiles()!!.asSequence().filter { |
| 64 | + it.extension !in backupConfiguration.purgeExtensionsExceptions && |
| 65 | + it.nameWithoutExtension !in backupConfiguration.purgeNamesExceptions && |
| 66 | + it.extension != "zip" |
| 67 | + }.forEach { it.deleteRecursively() } |
| 68 | + // @formatter:on |
| 69 | + } else logger.debug(marker, "Purging backup out directory skipped") |
| 70 | + } |
| 71 | + |
| 72 | + private fun rotate(out: File) { |
| 73 | + logger.debug(marker, "Starting rolling old backup files") |
| 74 | + out.listFiles()!!.asSequence().filter { |
| 75 | + it.extension == "zip" |
| 76 | + }.also { files -> |
| 77 | + if (files.count() > backupConfiguration.maxBackupFiles) { |
| 78 | + if (backupConfiguration.rollingBackupFilesEnabled) { |
| 79 | + files.map { it.lastModified() }.sorted().take( |
| 80 | + files.count() - backupConfiguration.maxBackupFiles |
| 81 | + ).forEach { date -> |
| 82 | + files.filter { it.lastModified() == date }.forEach { it.delete() } |
| 83 | + } |
| 84 | + } else files.forEach { it.delete() } |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + private fun compile(file: File) { |
| 90 | + runBlocking { |
| 91 | + launch(Dispatchers.Default) { |
| 92 | + try { |
| 93 | + getCurrentServer().save(false, true, true) |
| 94 | + } catch (ex: ConcurrentModificationException) { |
| 95 | + logger.error("Saving server world failed, backup will pack probably outdated data!") |
| 96 | + } |
| 97 | + }.also { |
| 98 | + it.invokeOnCompletion { |
| 99 | + val path = outPath(file).also { path -> logger.debug("Saving backup to $path") } |
| 100 | + val inPath = inPath() |
| 101 | + measureTimeMillis { |
| 102 | + ZipUtil.pack( |
| 103 | + File(inPath), File(path), backupConfiguration.backupCompressionLevel |
| 104 | + ) |
| 105 | + }.also { time -> logger.debug("Backup saved to $path for ${time / 1000} seconds") } |
| 106 | + } |
| 107 | + }.start() |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + private fun inPath(): String { |
| 112 | + var path = String.empty |
| 113 | + DistExecutor.runWhenOn(Dist.CLIENT) { |
| 114 | + Runnable { path = "saves${File.separator}${getCurrentServer().folderName}" } |
| 115 | + } |
| 116 | + DistExecutor.runWhenOn(Dist.DEDICATED_SERVER) { |
| 117 | + Runnable { path = getCurrentServer().folderName } |
| 118 | + } |
| 119 | + return if (path.isEmpty()) error("Backup `in` path was empty") else path |
| 120 | + } |
9 | 121 |
|
| 122 | + private fun outPath(file: File): String { |
| 123 | + val ext = ".zip" |
| 124 | + val dateTime = SimpleDateFormat(backupConfiguration.backupDateFormat).format(Date()) |
| 125 | + return file.absolutePath + File.separator + getCurrentServer().folderName + "-" + dateTime + ext |
10 | 126 | } |
11 | 127 | } |
0 commit comments