summaryrefslogtreecommitdiff
path: root/src/main/java/com/keuin/kbackupfabric/worker
diff options
context:
space:
mode:
authorKeuin <[email protected]>2020-04-23 14:51:29 +0800
committerkeuin <[email protected]>2020-04-23 14:51:29 +0800
commit720e8ec8eeb69d24afbb6b14068563cde2d470f0 (patch)
tree83b6dee213737b138ede00d83aa1e9d5eab51fa3 /src/main/java/com/keuin/kbackupfabric/worker
parent4605445eb90e15a0629cf937452054cab7dd2b85 (diff)
Version 1.1.0-dev:1.1.0-dev
- Optimized backup lag (using async I/O). - Added twice confirmation /kb confirm and cancellation /kb cancel, to avoid mistake. - Added countdown before restoring. - Adjusted some text. - Code optimization.
Diffstat (limited to 'src/main/java/com/keuin/kbackupfabric/worker')
-rw-r--r--src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java92
-rw-r--r--src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java90
2 files changed, 182 insertions, 0 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java b/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java
new file mode 100644
index 0000000..30fd8b5
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java
@@ -0,0 +1,92 @@
+package com.keuin.kbackupfabric.worker;
+
+import com.keuin.kbackupfabric.util.ZipUtil;
+import com.keuin.kbackupfabric.util.ZipUtilException;
+import com.mojang.brigadier.context.CommandContext;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.world.World;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*;
+import static com.keuin.kbackupfabric.util.PrintUtil.debug;
+import static com.keuin.kbackupfabric.util.PrintUtil.message;
+
+/**
+ * The backup worker
+ * To invoke this worker, simply call invoke() method.
+ */
+public final class BackupWorker implements Runnable {
+ private final CommandContext<ServerCommandSource> context;
+ private final String backupName;
+ private final Map<World, Boolean> oldWorldsSavingDisabled;
+
+
+ private BackupWorker(CommandContext<ServerCommandSource> context, String backupName, Map<World, Boolean> oldWorldsSavingDisabled) {
+ this.context = context;
+ this.backupName = backupName;
+ this.oldWorldsSavingDisabled = oldWorldsSavingDisabled;
+ }
+
+ public static void invoke(CommandContext<ServerCommandSource> context, String backupName) {
+ //// Save world, save old autosave configs
+
+ message(context, String.format("Making backup %s, please wait ...", backupName), true);
+ Map<World, Boolean> oldWorldsSavingDisabled = new HashMap<>(); // old switch stat
+
+ // Get server
+ MinecraftServer server = context.getSource().getMinecraftServer();
+
+ // Save old autosave switch stat temporally
+ server.getWorlds().forEach(world -> {
+ oldWorldsSavingDisabled.put(world, world.savingDisabled);
+ world.savingDisabled = true;
+ });
+
+ // Force to save all player data and worlds
+ debug("Saving players ...");
+ server.getPlayerManager().saveAllPlayerData();
+ debug("Saving worlds ...");
+ server.save(true, true, true);
+
+ // Start threaded worker
+ BackupWorker worker = new BackupWorker(context, backupName, oldWorldsSavingDisabled);
+ Thread workerThread = new Thread(worker, "BackupWorker");
+ workerThread.start();
+ }
+
+ @Override
+ public void run() {
+ String destPathFolderName = "";
+ MinecraftServer server = context.getSource().getMinecraftServer();
+ try {
+ //// Do our main backup logic
+
+ // Create backup saving directory
+ File destPathFile = getBackupSaveDirectory(server);
+ destPathFolderName = destPathFile.getName();
+ if (!destPathFile.isDirectory() && !destPathFile.mkdir()) {
+ message(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", destPathFolderName));
+ return;
+ }
+
+ // Make zip
+ String levelPath = getLevelPath(server);
+ debug(String.format("zip(srcPath=%s, destPath=%s)", levelPath, destPathFile.toString()));
+ ZipUtil.zip(levelPath, destPathFile.toString(), getBackupFileName(backupName));
+
+ // Restore old autosave switch stat
+ server.getWorlds().forEach(world -> world.savingDisabled = oldWorldsSavingDisabled.getOrDefault(world, true));
+
+ message(context, "Done.", true);
+ } catch (SecurityException e) {
+ message(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", destPathFolderName));
+ } catch (IOException | ZipUtilException e) {
+ message(context, "Failed to make zip: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
new file mode 100644
index 0000000..9e424d7
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
@@ -0,0 +1,90 @@
+package com.keuin.kbackupfabric.worker;
+
+import com.keuin.kbackupfabric.util.ZipUtil;
+import com.keuin.kbackupfabric.util.ZipUtilException;
+import net.minecraft.server.MinecraftServer;
+
+import java.io.File;
+import java.io.IOException;
+
+import static com.keuin.kbackupfabric.util.PrintUtil.*;
+import static org.apache.commons.io.FileUtils.forceDelete;
+
+/**
+ * The restore worker
+ * To invoke this worker, simply call invoke() method.
+ */
+public final class RestoreWorker implements Runnable {
+ private final Thread serverThread;
+ private final String backupFilePath;
+ private final String levelDirectory;
+
+ private RestoreWorker(Thread serverThread, String backupFilePath, String levelDirectory) {
+ this.serverThread = serverThread;
+ this.backupFilePath = backupFilePath;
+ this.levelDirectory = levelDirectory;
+ }
+
+ public static void invoke(MinecraftServer server, String backupFilePath, String levelDirectory) {
+ RestoreWorker worker = new RestoreWorker(server.getThread(), backupFilePath, levelDirectory);
+ Thread workerThread = new Thread(worker, "RestoreWorker");
+ workerThread.start();
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ignored) {
+ }
+ server.stop(false);
+ }
+
+ @Override
+ public void run() {
+ try {
+ // Wait server thread die
+ debug("Waiting server thread stopping ...");
+ while (serverThread.isAlive()) {
+ try {
+ serverThread.join();
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ debug("Wait for 5 seconds ...");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException ignored) {
+ }
+
+ // Delete old level
+ debug("Server stopped. Deleting old level ...");
+ File levelDirFile = new File(levelDirectory);
+
+ int failedCounter = 0;
+ final int MAX_RETRY_TIMES = 20;
+ while (failedCounter < MAX_RETRY_TIMES) {
+ System.gc();
+ if (!levelDirFile.delete() && levelDirFile.exists()) {
+ System.gc();
+ forceDelete(levelDirFile); // Try to force delete.
+ }
+ if (!levelDirFile.exists())
+ break;
+ ++failedCounter;
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ if (levelDirFile.exists()) {
+ error(String.format("Cannot restore: failed to delete old level %s .", levelDirFile.getName()));
+ return;
+ }
+
+ // Decompress archive
+ debug("Decompressing archived level");
+ ZipUtil.unzip(backupFilePath, levelDirectory, false);
+ info("Restore complete! Please restart the server manually.");
+ } catch (SecurityException | IOException | ZipUtilException e) {
+ error("An exception occurred while restoring: " + e.getMessage());
+ }
+ }
+}