From 7259de6c17af570a860aa243584d723be3805554 Mon Sep 17 00:00:00 2001 From: Logan Gorence Date: Tue, 21 Dec 2021 00:58:22 +0000 Subject: [PATCH] Convert to Kotlin. --- build.gradle.kts | 13 +- .../cloud/kubelet/foundation/Foundation.java | 59 ------- .../cloud/kubelet/foundation/TextColors.java | 7 - .../java/cloud/kubelet/foundation/Util.java | 29 ---- .../foundation/command/BackupCommand.java | 131 ---------------- .../cloud/kubelet/foundation/Foundation.kt | 57 +++++++ .../cloud/kubelet/foundation/TextColors.kt | 7 + .../kotlin/cloud/kubelet/foundation/Util.kt | 28 ++++ .../foundation/command/BackupCommand.kt | 144 ++++++++++++++++++ 9 files changed, 246 insertions(+), 229 deletions(-) delete mode 100644 src/main/java/cloud/kubelet/foundation/Foundation.java delete mode 100644 src/main/java/cloud/kubelet/foundation/TextColors.java delete mode 100644 src/main/java/cloud/kubelet/foundation/Util.java delete mode 100644 src/main/java/cloud/kubelet/foundation/command/BackupCommand.java create mode 100644 src/main/kotlin/cloud/kubelet/foundation/Foundation.kt create mode 100644 src/main/kotlin/cloud/kubelet/foundation/TextColors.kt create mode 100644 src/main/kotlin/cloud/kubelet/foundation/Util.kt create mode 100644 src/main/kotlin/cloud/kubelet/foundation/command/BackupCommand.kt diff --git a/build.gradle.kts b/build.gradle.kts index cef73a7..07f526a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - java - id("com.github.johnrengelman.shadow") version("7.1.1") + kotlin("jvm") version "1.6.10" + id("com.github.johnrengelman.shadow") version "7.1.1" } group = "io.gorence" @@ -19,8 +19,15 @@ repositories { } dependencies { - compileOnly("io.papermc.paper:paper-api:1.18.1-R0.1-SNAPSHOT") + // Kotlin dependencies + implementation(platform("org.jetbrains.kotlin:kotlin-bom")) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + // Database layer implementation("org.jetbrains.xodus:xodus-openAPI:1.3.232") + + // Paper API + compileOnly("io.papermc.paper:paper-api:1.18.1-R0.1-SNAPSHOT") } java { diff --git a/src/main/java/cloud/kubelet/foundation/Foundation.java b/src/main/java/cloud/kubelet/foundation/Foundation.java deleted file mode 100644 index 3997706..0000000 --- a/src/main/java/cloud/kubelet/foundation/Foundation.java +++ /dev/null @@ -1,59 +0,0 @@ -package cloud.kubelet.foundation; - -import cloud.kubelet.foundation.command.BackupCommand; -import io.papermc.paper.event.player.ChatEvent; -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Objects; -import net.kyori.adventure.text.Component; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.plugin.java.JavaPlugin; - -public final class Foundation extends JavaPlugin implements Listener { - public static final boolean BACKUP_ENABLED = true; - private static final String BACKUPS_DIRECTORY = "backups"; - - @Override - public void onEnable() { - Path dataPath = getDataFolder().toPath(); - Path backupPath = dataPath.resolve(BACKUPS_DIRECTORY); - - // Create Foundation plugin directories. - dataPath.toFile().mkdir(); - backupPath.toFile().mkdir(); - - // Register this as an event listener. - getServer().getPluginManager().registerEvents(this, this); - - // Set up commands. - Objects.requireNonNull(getCommand("fbackup")).setExecutor(new BackupCommand(backupPath)); - - final var log = getSLF4JLogger(); - log.info("Features:"); - Util.printFeatureStatus(log, "Backup: ", BACKUP_ENABLED); - } - - @Override - public void onDisable() { - } - - private final Component leftBracket = Component.text('['); - private final Component rightBracket = Component.text(']'); - - @EventHandler - private void onChatMessage(ChatEvent e) { - e.setCancelled(true); - - final var name = e.getPlayer().displayName(); - final var component = Component.empty() - .append(leftBracket) - .append(name) - .append(rightBracket) - .append(Component.text(' ')) - .append(e.message()); - - getServer().sendMessage(component); - } -} diff --git a/src/main/java/cloud/kubelet/foundation/TextColors.java b/src/main/java/cloud/kubelet/foundation/TextColors.java deleted file mode 100644 index 07e4f20..0000000 --- a/src/main/java/cloud/kubelet/foundation/TextColors.java +++ /dev/null @@ -1,7 +0,0 @@ -package cloud.kubelet.foundation; - -import net.kyori.adventure.text.format.TextColor; - -public final class TextColors { - public static final TextColor AMARANTH_PINK = TextColor.fromHexString("#F7A8B8"); -} diff --git a/src/main/java/cloud/kubelet/foundation/Util.java b/src/main/java/cloud/kubelet/foundation/Util.java deleted file mode 100644 index 1ccd49c..0000000 --- a/src/main/java/cloud/kubelet/foundation/Util.java +++ /dev/null @@ -1,29 +0,0 @@ -package cloud.kubelet.foundation; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; -import org.slf4j.Logger; - -public class Util { - - private static final Component leftBracket = Component.text('['); - private static final Component rightBracket = Component.text(']'); - private static final Component whitespace = Component.text(' '); - private static final Component foundationName = Component.text("Foundation"); - - public static void printFeatureStatus(Logger logger, String feature, boolean state) { - logger.info("{}: {}", feature, state ? "Enabled" : "Disabled"); - } - - public static Component formatSystemMessage(String message) { - return formatSystemMessage(TextColors.AMARANTH_PINK, message); - } - - public static Component formatSystemMessage(TextColor prefixColor, String message) { - return leftBracket - .append(foundationName.color(prefixColor)) - .append(rightBracket) - .append(whitespace) - .append(Component.text(message)); - } -} diff --git a/src/main/java/cloud/kubelet/foundation/command/BackupCommand.java b/src/main/java/cloud/kubelet/foundation/command/BackupCommand.java deleted file mode 100644 index 8136f11..0000000 --- a/src/main/java/cloud/kubelet/foundation/command/BackupCommand.java +++ /dev/null @@ -1,131 +0,0 @@ -package cloud.kubelet.foundation.command; - -import cloud.kubelet.foundation.Foundation; -import cloud.kubelet.foundation.Util; -import java.io.BufferedOutputStream; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; -import org.bukkit.Server; -import org.bukkit.World; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -public class BackupCommand implements CommandExecutor { - - private static final AtomicBoolean RUNNING = new AtomicBoolean(); - private final Path backupPath; - - public BackupCommand(Path backupPath) { - this.backupPath = backupPath; - } - - @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, - @NotNull String label, @NotNull String[] args) { - if (!Foundation.BACKUP_ENABLED) { - sender.sendMessage( - Component - .text("Backup is not enabled.") - .color(TextColor.fromHexString("#FF0000")) - ); - return true; - } - if (RUNNING.get()) { - sender.sendMessage( - Component - .text("Backup is already running.") - .color(TextColor.fromHexString("#FF0000")) - ); - } else { - try { - runBackup(sender); - } catch (Exception e) { - sender.sendMessage(String.format("Failed to backup: %s", e.getMessage())); - } - } - - return true; - } - - private void runBackup(CommandSender sender) throws IOException { - RUNNING.set(true); - - final var server = sender.getServer(); - server.sendMessage(Util.formatSystemMessage("Backup started.")); - - final var backupFile = backupPath.resolve( - String.format("backup-%s.zip", Instant.now().toString())).toFile(); - final var zipFileStream = new FileOutputStream(backupFile); - final var zipStream = new ZipOutputStream(new BufferedOutputStream(zipFileStream)); - - try (zipFileStream; zipStream) { - backupPlugins(server, zipStream); - backupWorlds(server, zipStream); - } finally { - RUNNING.set(false); - server.sendMessage(Util.formatSystemMessage("Backup finished.")); - } - } - - private void backupPlugins(Server server, ZipOutputStream zipStream) { - try { - addDirectoryToZip(zipStream, server.getPluginsFolder().toPath()); - } catch (IOException e) { - // TODO: Add error handling. - e.printStackTrace(); - } - } - - private void backupWorlds(Server server, ZipOutputStream zipStream) { - final var worlds = server.getWorlds(); - for (World world : worlds) { - final var worldPath = world.getWorldFolder().toPath(); - - // Save the world. - world.save(); - - // Disable auto saving to prevent any world corruption while creating a ZIP. - world.setAutoSave(false); - - try { - addDirectoryToZip(zipStream, worldPath); - } catch (IOException e) { - // TODO: Add error handling. - e.printStackTrace(); - } - - // Re-enable auto saving for this world. - world.setAutoSave(true); - } - } - - private void addDirectoryToZip(ZipOutputStream zipStream, Path directoryPath) throws IOException { - final var paths = Files.walk(directoryPath) - .filter(Files::isRegularFile) - .toList(); - - for (Path path : paths) { - try (InputStream fileStream = new FileInputStream(path.toFile())) { - final var entry = new ZipEntry(path.toString()); - zipStream.putNextEntry(entry); - int n; - byte[] buffer = new byte[1024]; - while ((n = fileStream.read(buffer)) > -1) { - zipStream.write(buffer, 0, n); - } - } - } - } -} diff --git a/src/main/kotlin/cloud/kubelet/foundation/Foundation.kt b/src/main/kotlin/cloud/kubelet/foundation/Foundation.kt new file mode 100644 index 0000000..045aa4c --- /dev/null +++ b/src/main/kotlin/cloud/kubelet/foundation/Foundation.kt @@ -0,0 +1,57 @@ +package cloud.kubelet.foundation + +import cloud.kubelet.foundation.command.BackupCommand +import io.papermc.paper.event.player.ChatEvent +import net.kyori.adventure.text.Component +import org.bukkit.command.CommandExecutor +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.plugin.java.JavaPlugin + +class Foundation : JavaPlugin(), Listener { + override fun onEnable() { + val dataPath = dataFolder.toPath() + val backupPath = dataPath.resolve(BACKUPS_DIRECTORY) + + // Create Foundation plugin directories. + dataPath.toFile().mkdir() + backupPath.toFile().mkdir() + + // Register this as an event listener. + server.pluginManager.registerEvents(this, this) + + // Register commands. + registerCommandExecutor("fbackup", BackupCommand(this, backupPath)) + + val log = slF4JLogger + log.info("Features:") + Util.printFeatureStatus(log, "Backup: ", BACKUP_ENABLED) + } + + private fun registerCommandExecutor(name: String, executor: CommandExecutor) { + val command = getCommand(name) ?: throw Exception("Failed to get $name command") + command.setExecutor(executor) + } + + @EventHandler + private fun onChatMessage(e: ChatEvent) { + e.isCancelled = true + val name = e.player.displayName() + val component = Component.empty() + .append(leftBracket) + .append(name) + .append(rightBracket) + .append(Component.text(' ')) + .append(e.message()) + server.sendMessage(component) + } + + companion object { + private const val BACKUPS_DIRECTORY = "backups" + + private val leftBracket: Component = Component.text('[') + private val rightBracket: Component = Component.text(']') + + const val BACKUP_ENABLED = true + } +} \ No newline at end of file diff --git a/src/main/kotlin/cloud/kubelet/foundation/TextColors.kt b/src/main/kotlin/cloud/kubelet/foundation/TextColors.kt new file mode 100644 index 0000000..a2d8258 --- /dev/null +++ b/src/main/kotlin/cloud/kubelet/foundation/TextColors.kt @@ -0,0 +1,7 @@ +package cloud.kubelet.foundation + +import net.kyori.adventure.text.format.TextColor + +object TextColors { + val AMARANTH_PINK = TextColor.fromHexString("#F7A8B8") +} \ No newline at end of file diff --git a/src/main/kotlin/cloud/kubelet/foundation/Util.kt b/src/main/kotlin/cloud/kubelet/foundation/Util.kt new file mode 100644 index 0000000..4cc0e78 --- /dev/null +++ b/src/main/kotlin/cloud/kubelet/foundation/Util.kt @@ -0,0 +1,28 @@ +package cloud.kubelet.foundation + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor +import org.slf4j.Logger + +object Util { + private val leftBracket: Component = Component.text('[') + private val rightBracket: Component = Component.text(']') + private val whitespace: Component = Component.text(' ') + private val foundationName: Component = Component.text("Foundation") + + fun printFeatureStatus(logger: Logger, feature: String?, state: Boolean) { + logger.info("{}: {}", feature, if (state) "Enabled" else "Disabled") + } + + fun formatSystemMessage(message: String?): Component { + return formatSystemMessage(TextColors.AMARANTH_PINK, message) + } + + fun formatSystemMessage(prefixColor: TextColor?, message: String?): Component { + return leftBracket + .append(foundationName.color(prefixColor)) + .append(rightBracket) + .append(whitespace) + .append(Component.text(message!!)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cloud/kubelet/foundation/command/BackupCommand.kt b/src/main/kotlin/cloud/kubelet/foundation/command/BackupCommand.kt new file mode 100644 index 0000000..8a234ce --- /dev/null +++ b/src/main/kotlin/cloud/kubelet/foundation/command/BackupCommand.kt @@ -0,0 +1,144 @@ +package cloud.kubelet.foundation.command + +import cloud.kubelet.foundation.Foundation +import cloud.kubelet.foundation.Util +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.TextColor +import org.bukkit.Server +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import java.io.BufferedOutputStream +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.time.Instant +import java.util.concurrent.atomic.AtomicBoolean +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +class BackupCommand( + private val plugin: Foundation, + private val backupPath: Path +) : CommandExecutor { + override fun onCommand( + sender: CommandSender, command: Command, + label: String, args: Array + ): Boolean { + if (!Foundation.BACKUP_ENABLED) { + sender.sendMessage( + Component + .text("Backup is not enabled.") + .color(TextColor.fromHexString("#FF0000")) + ) + return true + } + + if (RUNNING.get()) { + sender.sendMessage( + Component + .text("Backup is already running.") + .color(TextColor.fromHexString("#FF0000")) + ) + return true + } + + try { + val server = sender.server + server.scheduler.runTaskAsynchronously(plugin, Runnable { + runBackup(server) + }) + } catch (e: Exception) { + sender.sendMessage(String.format("Failed to backup: %s", e.message)) + } + + return true + } + + private fun runBackup(server: Server) { + RUNNING.set(true) + + server.sendMessage(Util.formatSystemMessage("Backup started.")) + + val backupFile = + backupPath.resolve(String.format("backup-%s.zip", Instant.now().toString())).toFile() + + try { + FileOutputStream(backupFile).use { zipFileStream -> + ZipOutputStream(BufferedOutputStream(zipFileStream)).use { zipStream -> + backupPlugins(server, zipStream) + backupWorlds(server, zipStream) + } + } + } finally { + RUNNING.set(false) + server.sendMessage(Util.formatSystemMessage("Backup finished.")) + } + } + + private fun backupPlugins(server: Server, zipStream: ZipOutputStream) { + try { + addDirectoryToZip(zipStream, server.pluginsFolder.toPath()) + } catch (e: IOException) { + // TODO: Add error handling. + e.printStackTrace() + } + } + + private fun backupWorlds(server: Server, zipStream: ZipOutputStream) { + val worlds = server.worlds + for (world in worlds) { + val worldPath = world.worldFolder.toPath() + + // Save the world. + server.scheduler.runTask(plugin, Runnable { + world.save() + }) + + // Disable auto saving to prevent any world corruption while creating a ZIP. + world.isAutoSave = false + try { + addDirectoryToZip(zipStream, worldPath) + } catch (e: IOException) { + // TODO: Add error handling. + e.printStackTrace() + } + + // Re-enable auto saving for this world. + world.isAutoSave = true + } + } + + private fun addDirectoryToZip(zipStream: ZipOutputStream, directoryPath: Path) { + val paths = Files.walk(directoryPath) + .filter { path: Path? -> Files.isRegularFile(path) } + .toList() + val buffer = ByteArray(1024) + val backupsPath = backupPath.toRealPath() + + for (path in paths) { + val realPath = path.toRealPath() + + if (realPath.startsWith(backupsPath)) { + plugin.slF4JLogger.info("Skipping file for backup: {}", realPath) + continue + } + + FileInputStream(path.toFile()).use { fileStream -> + val entry = ZipEntry(path.toString()) + zipStream.putNextEntry(entry) + + var n: Int + while (fileStream.read(buffer).also { n = it } > -1) { + zipStream.write(buffer, 0, n) + } + } + } + } + + companion object { + private val RUNNING = AtomicBoolean() + } +} \ No newline at end of file