summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java141
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java18
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBMain.java2
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java2
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java53
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/PermissionValidator.java9
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/PrintUtil.java (renamed from src/main/java/com/keuin/kbackupfabric/util/IO.java)7
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java4
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java15
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java2
-rw-r--r--src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java92
-rw-r--r--src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java (renamed from src/main/java/com/keuin/kbackupfabric/util/PostProgressRestoreThread.java)39
12 files changed, 253 insertions, 131 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java b/src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java
index 33d0f43..943fb68 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandHandler.java
@@ -1,32 +1,28 @@
package com.keuin.kbackupfabric;
-import com.keuin.kbackupfabric.util.PostProgressRestoreThread;
-import com.keuin.kbackupfabric.util.ZipUtil;
-import com.keuin.kbackupfabric.util.ZipUtilException;
+import com.keuin.kbackupfabric.worker.BackupWorker;
+import com.keuin.kbackupfabric.worker.RestoreWorker;
import com.mojang.brigadier.arguments.StringArgumentType;
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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
-import java.util.Map;
-import static com.keuin.kbackupfabric.util.IO.debug;
-import static com.keuin.kbackupfabric.util.IO.message;
+import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*;
+import static com.keuin.kbackupfabric.util.PrintUtil.debug;
+import static com.keuin.kbackupfabric.util.PrintUtil.message;
-public class KBCommandHandler {
+public final class KBCommandHandler {
private static final int SUCCESS = 1;
private static final int FAILED = -1;
- private static final String backupSaveDirectoryName = "backups";
- private static final String backupFileNamePrefix = "kbackup-";
+
private static final HashMap<Integer, String> backupIndexNameMapper = new HashMap<>(); // index -> backupName
private static String restoreBackupNameToBeConfirmed = null;
@@ -37,11 +33,13 @@ public class KBCommandHandler {
* @return stat code.
*/
public static int help(CommandContext<ServerCommandSource> context) {
- message(context, "KBackup Manual");
- message(context, "/kb | /kb help Print help menu.");
+ message(context, "==== KBackup Manual ====");
+ message(context, "/kb /kb help Print help menu.");
message(context, "/kb list Show all backups.");
message(context, "/kb backup [backup_name] Backup world, nether, end to backup_name. By default, the name is current system time.");
message(context, "/kb restore <backup_name> Delete current three worlds, restore the older version from given backup. By default, this command is identical with /kb list.");
+ message(context, "/kb confirm Confirm and start restoring.");
+ message(context, "/kb cancel Cancel the restoration to be confirmed. If cancelled, /kb confirm will not effect without another valid /kb restore command.");
return SUCCESS;
}
@@ -49,7 +47,7 @@ public class KBCommandHandler {
message(context, "Available backups: (file is not checked, manipulation may affect this plugin)");
MinecraftServer server = context.getSource().getMinecraftServer();
File[] files = getBackupSaveDirectory(server).listFiles(
- (dir, name) -> dir.isDirectory() && name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(backupFileNamePrefix)
+ (dir, name) -> dir.isDirectory() && name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix())
);
backupIndexNameMapper.clear();
if (files != null) {
@@ -90,6 +88,7 @@ public class KBCommandHandler {
*/
public static int restore(CommandContext<ServerCommandSource> context) {
//KBMain.restore("name")
+ MinecraftServer server = context.getSource().getMinecraftServer();
String backupName = StringArgumentType.getString(context, "backupName");
if (backupName.matches("[0-9]*")) {
@@ -102,6 +101,13 @@ public class KBCommandHandler {
backupName = realBackupName; // Replace input number with real backup name.
}
+ // Validate backupName
+ if (!isBackupNameValid(backupName, server)) {
+ // Invalid backupName
+ message(context, "Invalid backup name! Please check your input. The list index number is also valid.", false);
+ return FAILED;
+ }
+
// Update confirm pending variable
restoreBackupNameToBeConfirmed = backupName;
message(context, String.format("WARNING: You will LOST YOUR CURRENT WORLD COMPLETELY! It will be replaced with the backup %s . Please use /kb confirm to proceed executing.", restoreBackupNameToBeConfirmed), true);
@@ -123,74 +129,8 @@ public class KBCommandHandler {
}
private static int doBackup(CommandContext<ServerCommandSource> context, String backupName) {
- String destPathFolderName = "";
- try {
- 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);
-
- //// Do our main backup logic
-
- // Create backup saving directory
- File destPathFile = getBackupSaveDirectory(server);
- destPathFolderName = destPathFile.getName();
- if (!destPathFile.mkdir() && !destPathFile.isDirectory()) {
- message(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", destPathFolderName));
- return FAILED;
- }
-
- // 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);
- return SUCCESS;
- } catch (SecurityException e) {
- message(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", destPathFolderName));
- return FAILED;
- } catch (IOException | ZipUtilException e) {
- message(context, "Failed to make zip: " + e.getMessage());
- return FAILED;
- }
- }
-
- private static String getBackupFileName(String backupName) {
- return backupFileNamePrefix + backupName + ".zip";
- }
-
- private static String getBackupName(String backupFileName) {
- try {
- if (backupFileName.matches(backupFileNamePrefix + ".+\\.zip"))
- return backupFileName.substring(backupFileNamePrefix.length(), backupFileName.length() - 4);
- } catch (IndexOutOfBoundsException ignored) {
- }
- return backupFileName;
- }
-
- private static File getBackupSaveDirectory(MinecraftServer server) {
- return new File(server.getRunDirectory(), backupSaveDirectoryName);
- }
-
- private static String getLevelPath(MinecraftServer server) {
- return (new File(server.getRunDirectory(), server.getLevelName())).getAbsolutePath();
+ BackupWorker.invoke(context, backupName);
+ return SUCCESS;
}
/**
@@ -214,18 +154,35 @@ public class KBCommandHandler {
String backupFileName = getBackupFileName(backupName);
debug("Backup file name: " + backupFileName);
File backupFile = new File(getBackupSaveDirectory(server), backupFileName);
- PostProgressRestoreThread postProgressRestoreThread = new PostProgressRestoreThread(server.getThread(), backupFile.getPath(), getLevelPath(server));
- Thread postThread = new Thread(postProgressRestoreThread, "PostProgressRestoreThread");
- postThread.start();
- server.stop(false);
- message(context, "Decompressing archive data. Server will shutdown to replace level data. Please do not restart the server.", true);
+ message(context, "Server will shutdown in a few seconds, depended on your world size and the disk speed, the restore progress may take seconds or minutes.", true);
+ message(context, "Please do not force the server stop, or the level would be broken.", true);
+ message(context, "After it shuts down, please restart the server manually.", true);
+ final int WAIT_SECONDS = 10;
+ for (int i = 0; i < WAIT_SECONDS; ++i) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ message(context, "Shutting down ...", true);
+ RestoreWorker.invoke(server, backupFile.getPath(), getLevelPath(server));
return SUCCESS;
}
- static boolean opPermissionValidator(ServerCommandSource commandSource) {
- return commandSource.hasPermissionLevel(4);
+ /**
+ * Cancel the execution to be confirmed.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int cancel(CommandContext<ServerCommandSource> context) {
+ if (restoreBackupNameToBeConfirmed != null) {
+ restoreBackupNameToBeConfirmed = null;
+ message(context, "The restoration is cancelled.", true);
+ return SUCCESS;
+ } else {
+ message(context, "Nothing to cancel.");
+ return FAILED;
+ }
}
-
-
-
}
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
index d4823c4..0407520 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
@@ -1,11 +1,12 @@
package com.keuin.kbackupfabric;
+import com.keuin.kbackupfabric.util.PermissionValidator;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
-public class KBCommandRegister {
+public final class KBCommandRegister {
// First make method to register
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
@@ -13,17 +14,20 @@ public class KBCommandRegister {
dispatcher.register(CommandManager.literal("kb").executes(KBCommandHandler::help));
dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("help").executes(KBCommandHandler::help)));
- // register /kb list for showing the backup list
- dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("list").executes(KBCommandHandler::list)));
+ // register /kb list for showing the backup list. OP is required.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("list").requires(PermissionValidator::op).executes(KBCommandHandler::list)));
// register /kb backup [name] for performing backup. OP is required.
- dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("backup").then(CommandManager.argument("backupName", StringArgumentType.string()).requires(KBCommandHandler::opPermissionValidator).executes(KBCommandHandler::backup)).requires(KBCommandHandler::opPermissionValidator).executes(KBCommandHandler::backupWithDefaultName)));
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("backup").then(CommandManager.argument("backupName", StringArgumentType.string()).requires(PermissionValidator::op).executes(KBCommandHandler::backup)).requires(PermissionValidator::op).executes(KBCommandHandler::backupWithDefaultName)));
// register /kb restore <name> for performing restore. OP is required.
- dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("restore").then(CommandManager.argument("backupName", StringArgumentType.string()).requires(KBCommandHandler::opPermissionValidator).executes(KBCommandHandler::restore)).executes(KBCommandHandler::list)));
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("restore").then(CommandManager.argument("backupName", StringArgumentType.string()).requires(PermissionValidator::op).executes(KBCommandHandler::restore)).executes(KBCommandHandler::list)));
- // register /kb confirm for confirming the execution
- dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("confirm").executes(KBCommandHandler::confirm)));
+ // register /kb confirm for confirming the execution. OP is required.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("confirm").requires(PermissionValidator::op).executes(KBCommandHandler::confirm)));
+
+ // register /kb cancel for cancelling the execution to be confirmed. OP is required.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("cancel").requires(PermissionValidator::op).executes(KBCommandHandler::cancel)));
// LiteralCommandNode<ServerCommandSource> basenode = dispatcher.register(literal("findBiome")
// .then(argument("biome_identifier", identifier()).suggests(BiomeCompletionProvider.BIOMES) // We use Biome suggestions for identifier argument
diff --git a/src/main/java/com/keuin/kbackupfabric/KBMain.java b/src/main/java/com/keuin/kbackupfabric/KBMain.java
index f2a3789..f8a61f1 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBMain.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBMain.java
@@ -1,6 +1,6 @@
package com.keuin.kbackupfabric;
-public class KBMain {
+public final class KBMain {
/**
* Perform real backup process.
*
diff --git a/src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java b/src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java
index 684ec07..c3582c7 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBPluginInitializer.java
@@ -3,7 +3,7 @@ package com.keuin.kbackupfabric;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.registry.CommandRegistry;
-public class KBPluginInitializer implements ModInitializer {
+public final class KBPluginInitializer implements ModInitializer {
@Override
public void onInitialize() {
System.out.println("Initializing KBackup...");
diff --git a/src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java b/src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java
new file mode 100644
index 0000000..5b8ba5a
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/BackupFilesystemUtil.java
@@ -0,0 +1,53 @@
+package com.keuin.kbackupfabric.util;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.world.ThreadedAnvilChunkStorage;
+import net.minecraft.world.World;
+
+import java.io.File;
+
+/**
+ * Functions deal with file name, directory name about Minecraft saves.
+ */
+public final class BackupFilesystemUtil {
+
+ private static final String backupSaveDirectoryName = "backups";
+ private static final String backupFileNamePrefix = "kbackup-";
+
+ public static String getBackupFileName(String backupName) {
+ return backupFileNamePrefix + backupName + ".zip";
+ }
+
+ public static String getBackupName(String backupFileName) {
+ try {
+ if (backupFileName.matches(backupFileNamePrefix + ".+\\.zip"))
+ return backupFileName.substring(backupFileNamePrefix.length(), backupFileName.length() - 4);
+ } catch (IndexOutOfBoundsException ignored) {
+ }
+ return backupFileName;
+ }
+
+ public static boolean isBackupNameValid(String backupName, MinecraftServer server) {
+ File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName));
+ return backupFile.isFile();
+ }
+
+ public static File getBackupSaveDirectory(MinecraftServer server) {
+ return new File(server.getRunDirectory(), backupSaveDirectoryName);
+ }
+
+ public static String getLevelPath(MinecraftServer server) {
+ return (new File(server.getRunDirectory(), server.getLevelName())).getAbsolutePath();
+ }
+
+ public static String getWorldDirectoryName(World world) throws NoSuchFieldException, IllegalAccessException {
+ File saveDir;
+ ThreadedAnvilChunkStorage threadedAnvilChunkStorage = (ThreadedAnvilChunkStorage) ReflectionUtils.getPrivateField(world.getChunkManager(), "threadedAnvilChunkStorage");
+ saveDir = (File) ReflectionUtils.getPrivateField(threadedAnvilChunkStorage, "saveDir");
+ return saveDir.getName();
+ }
+
+ public static String getBackupFileNamePrefix() {
+ return backupFileNamePrefix;
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/PermissionValidator.java b/src/main/java/com/keuin/kbackupfabric/util/PermissionValidator.java
new file mode 100644
index 0000000..9d37901
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/PermissionValidator.java
@@ -0,0 +1,9 @@
+package com.keuin.kbackupfabric.util;
+
+import net.minecraft.server.command.ServerCommandSource;
+
+public class PermissionValidator {
+ public static boolean op(ServerCommandSource commandSource) {
+ return commandSource.hasPermissionLevel(4);
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/IO.java b/src/main/java/com/keuin/kbackupfabric/util/PrintUtil.java
index 6d969ba..e76155f 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/IO.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/PrintUtil.java
@@ -6,7 +6,7 @@ import net.minecraft.text.LiteralText;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-public class IO {
+public final class PrintUtil {
private static final Logger LOGGER = LogManager.getLogger();
private static final boolean printDebugMessages = true;
@@ -15,13 +15,16 @@ public class IO {
private static final Object syncDebug = new Object();
private static final Object syncError = new Object();
private static final Object syncInfo = new Object();
+ private static final Object syncMessage = new Object();
public static CommandContext<ServerCommandSource> message(CommandContext<ServerCommandSource> context, String messageText) {
return message(context, messageText, false);
}
public static CommandContext<ServerCommandSource> message(CommandContext<ServerCommandSource> context, String messageText, boolean broadcastToOps) {
- context.getSource().sendFeedback(new LiteralText("[KBackup] " + messageText), broadcastToOps);
+ synchronized (syncMessage) {
+ context.getSource().sendFeedback(new LiteralText("[KBackup] " + messageText), broadcastToOps);
+ }
return context;
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java b/src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java
index f7bc351..2acc562 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/ReflectionUtils.java
@@ -7,9 +7,9 @@ import java.lang.reflect.Method;
/**
* @Author 落叶飞翔的蜗牛
* @Date 2018/3/10
- * @Description
+ * @Description 常用反射函数
*/
-public class ReflectionUtils {
+public final class ReflectionUtils {
/**
* 获取私有成员变量的值
diff --git a/src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java b/src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java
deleted file mode 100644
index badc068..0000000
--- a/src/main/java/com/keuin/kbackupfabric/util/WorldUtil.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.keuin.kbackupfabric.util;
-
-import net.minecraft.server.world.ThreadedAnvilChunkStorage;
-import net.minecraft.world.World;
-
-import java.io.File;
-
-public class WorldUtil {
- public static String getWorldDirectoryName(World world) throws NoSuchFieldException, IllegalAccessException {
- File saveDir;
- ThreadedAnvilChunkStorage threadedAnvilChunkStorage = (ThreadedAnvilChunkStorage) ReflectionUtils.getPrivateField(world.getChunkManager(), "threadedAnvilChunkStorage");
- saveDir = (File) ReflectionUtils.getPrivateField(threadedAnvilChunkStorage, "saveDir");
- return saveDir.getName();
- }
-}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
index 0ab93ca..1ceeb60 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
@@ -4,7 +4,7 @@ import java.io.*;
import java.util.Enumeration;
import java.util.zip.*;
-public class ZipUtil {
+public final class ZipUtil {
/**
* 递归压缩文件夹
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/util/PostProgressRestoreThread.java b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
index 8427a2c..9e424d7 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/PostProgressRestoreThread.java
+++ b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
@@ -1,26 +1,41 @@
-package com.keuin.kbackupfabric.util;
+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.IO.*;
+import static com.keuin.kbackupfabric.util.PrintUtil.*;
import static org.apache.commons.io.FileUtils.forceDelete;
/**
- * This thread wait the server to be stopped (must invoke stop out of this thread),
- * then delete current level, and restore our backup.
+ * The restore worker
+ * To invoke this worker, simply call invoke() method.
*/
-public class PostProgressRestoreThread implements Runnable {
+public final class RestoreWorker implements Runnable {
private final Thread serverThread;
private final String backupFilePath;
private final String levelDirectory;
- public PostProgressRestoreThread(Thread serverThread, String backupFilePath, 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 {
@@ -33,11 +48,12 @@ public class PostProgressRestoreThread implements Runnable {
}
}
- debug("Waiting ...");
+ 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);
@@ -50,10 +66,13 @@ public class PostProgressRestoreThread implements Runnable {
System.gc();
forceDelete(levelDirFile); // Try to force delete.
}
- if (levelDirFile.exists())
- ++failedCounter;
- else
+ 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()));