VoidPlugin/VoidBackup/src/main/kotlin/nl/voidcorp/backupplugin/VoidBackup.kt

133 lines
4.5 KiB
Kotlin

package nl.voidcorp.backupplugin
import com.github.luben.zstd.ZstdOutputStream
import nl.voidcorp.mainplugin.CommandHandler
import nl.voidcorp.mainplugin.VoidPluginBase
import nl.voidcorp.mainplugin.adapter
import nl.voidcorp.mainplugin.commands.VoidCommand
import nl.voidcorp.mainplugin.messaging.Message
import nl.voidcorp.mainplugin.messaging.MessageType
import nl.voidcorp.mainplugin.moshi
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.scheduler.BukkitRunnable
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import org.kamranzafar.jtar.TarEntry
import org.kamranzafar.jtar.TarOutputStream
import java.io.File
import java.nio.file.Files
import java.util.*
import kotlin.concurrent.thread
import kotlin.math.ln
import kotlin.math.pow
import kotlin.streams.toList
class VoidBackup(override val comment: String = "Make Backups!") : VoidPluginBase() {
private val backupDir = File(server.worldContainer, "backups").apply {
if (!exists()) mkdirs()
}
private val backupTask = BackupTask(this)
lateinit var conf: Config
override fun enable() {
send("VoidPlugin", MessageType.GET_CONFIG)
send("VoidPlugin", MessageType.POST_CONFIG, moshi.adapter<Config>().toJson(conf))
backupTask.runTaskTimer(this, 20, conf.timeoutMinutes * 60 * 20)
CommandHandler(this).registerCommand("force-backup", ForceBackupCommand(this))
}
override fun disable() {
backupTask.cancel()
}
override fun recieve(message: Message) {
if (message.messageType == MessageType.GET_CONFIG) {
conf = moshi.adapter<Config>().fromJson(message.content)!!
}
}
fun backup() {
Bukkit.broadcastMessage("Backing up. Server may lag for a bit...")
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "save-off")
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "save-all")
val done = mutableListOf<UUID>()
server.worlds.forEach { world ->
val dir = world.worldFolder
val whereTo = createTempFile("yeet")
val whereToZ = File(
backupDir,
"${world.name}-${DateTime.now().toString(ISODateTimeFormat.basicDateTimeNoMillis())}.tar.zst"
)
thread {
val files = Files.walk(dir.toPath()).map { it.toFile() }.filter { !it.isDirectory }.toList()
val tar = TarOutputStream(whereTo)
tar.use { tarOS ->
for (f in files) {
tarOS.putNextEntry(TarEntry(f, f.path.removePrefix(".${File.separatorChar}")))
f.inputStream().use {
it.copyTo(tarOS)
}
}
}
val zstOutputStream = ZstdOutputStream(whereToZ.outputStream())
val inp = whereTo.inputStream()
zstOutputStream.use { zst ->
inp.use { tr ->
tr.copyTo(zst)
}
}
Bukkit.broadcastMessage("World ${world.name} has been saved, it is ${humanReadableByteCount(whereToZ.length())}")
done.add(world.uid)
if (server.worlds.size == done.size) {
Bukkit.getScheduler().runTask(this, EnableSave)
}
}
}
}
private fun humanReadableByteCount(bytes: Long, si: Boolean = false): String {
val unit = if (si) 1000 else 1024
if (bytes < unit) return "$bytes B"
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
}
object EnableSave : Runnable {
override fun run() {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "save-on")
}
}
class BackupTask(private val pl: VoidBackup) : BukkitRunnable() {
override fun run() {
pl.backup()
}
}
data class Config(val timeoutMinutes: Long = 30)
class ForceBackupCommand(private val pl: VoidBackup) : VoidCommand() {
override fun onCommand(
sender: CommandSender,
command: Command,
label: String,
args: Array<out String>
): Boolean {
pl.backup()
return true
}
}
}