Convert to Kotlin.

This commit is contained in:
Logan Gorence 2021-12-21 00:58:22 +00:00
parent 9413211a82
commit 7259de6c17
No known key found for this signature in database
GPG Key ID: 9743CEF10935949A
9 changed files with 246 additions and 229 deletions

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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");
}

View File

@ -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));
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,7 @@
package cloud.kubelet.foundation
import net.kyori.adventure.text.format.TextColor
object TextColors {
val AMARANTH_PINK = TextColor.fromHexString("#F7A8B8")
}

View File

@ -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!!))
}
}

View File

@ -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<String>
): 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()
}
}