diff options
author | Keuin <[email protected]> | 2020-04-23 14:51:29 +0800 |
---|---|---|
committer | keuin <[email protected]> | 2020-04-23 14:51:29 +0800 |
commit | 720e8ec8eeb69d24afbb6b14068563cde2d470f0 (patch) | |
tree | 83b6dee213737b138ede00d83aa1e9d5eab51fa3 | |
parent | 4605445eb90e15a0629cf937452054cab7dd2b85 (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.
14 files changed, 258 insertions, 135 deletions
@@ -16,6 +16,7 @@ commands: To-Do List: -- Optimize lag during the backup process (use async I/O) -- Optimize output format -- Implement incremental backup +- Optimize output format. +- Add backup name completion. +- Optimize the backup name output. (add datetime output automatically, name is essential and is separated from the datetime) +- Implement incremental backup.
\ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 7b5f0c9..e6e62df 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.14.4 yarn_mappings=1.14.4+build.16 loader_version=0.8.2+build.194 # Mod Properties -mod_version=1.0.0 +mod_version=1.1.0-dev maven_group=com.keuin.kbackupfabric archives_base_name=kbackup-fabric # Dependencies 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())); |