mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Convert to Kotlin.
This commit is contained in:
		@ -1,6 +1,6 @@
 | 
				
			|||||||
plugins {
 | 
					plugins {
 | 
				
			||||||
  java
 | 
					  kotlin("jvm") version "1.6.10"
 | 
				
			||||||
  id("com.github.johnrengelman.shadow") version("7.1.1")
 | 
					  id("com.github.johnrengelman.shadow") version "7.1.1"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
group = "io.gorence"
 | 
					group = "io.gorence"
 | 
				
			||||||
@ -19,8 +19,15 @@ repositories {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					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")
 | 
					  implementation("org.jetbrains.xodus:xodus-openAPI:1.3.232")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Paper API
 | 
				
			||||||
 | 
					  compileOnly("io.papermc.paper:paper-api:1.18.1-R0.1-SNAPSHOT")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
java {
 | 
					java {
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -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");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -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));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										57
									
								
								src/main/kotlin/cloud/kubelet/foundation/Foundation.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/main/kotlin/cloud/kubelet/foundation/Foundation.kt
									
									
									
									
									
										Normal 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
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/main/kotlin/cloud/kubelet/foundation/TextColors.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/main/kotlin/cloud/kubelet/foundation/TextColors.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.kyori.adventure.text.format.TextColor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object TextColors {
 | 
				
			||||||
 | 
					  val AMARANTH_PINK = TextColor.fromHexString("#F7A8B8")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										28
									
								
								src/main/kotlin/cloud/kubelet/foundation/Util.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/main/kotlin/cloud/kubelet/foundation/Util.kt
									
									
									
									
									
										Normal 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!!))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user