summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommands.java31
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java2
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java20
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java (renamed from src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java)76
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java27
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java92
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java53
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java58
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java11
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java12
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java66
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java32
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java39
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java9
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java5
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java5
-rw-r--r--src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java95
17 files changed, 449 insertions, 184 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
index 8a1a7cc..eb7e000 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
@@ -2,12 +2,14 @@ package com.keuin.kbackupfabric;
import com.keuin.kbackupfabric.metadata.BackupMetadata;
import com.keuin.kbackupfabric.metadata.MetadataHolder;
-import com.keuin.kbackupfabric.operation.AbstractConfirmableOperation;
+import com.keuin.kbackupfabric.operation.BackupOperation;
+import com.keuin.kbackupfabric.operation.DeleteOperation;
+import com.keuin.kbackupfabric.operation.RestoreOperation;
+import com.keuin.kbackupfabric.operation.abstracts.i.Invokable;
import com.keuin.kbackupfabric.util.BackupFilesystemUtil;
import com.keuin.kbackupfabric.util.BackupNameSuggestionProvider;
import com.keuin.kbackupfabric.util.BackupNameTimeFormatter;
import com.keuin.kbackupfabric.util.PrintUtil;
-import com.keuin.kbackupfabric.worker.BackupWorker;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.MinecraftServer;
@@ -31,7 +33,7 @@ public final class KBCommands {
//private static final Logger LOGGER = LogManager.getLogger();
private static final List<String> backupNameList = new ArrayList<>(); // index -> backupName
- private static AbstractConfirmableOperation pendingOperation = null;
+ private static Invokable pendingOperation = null;
/**
* Print the help menu.
@@ -130,7 +132,8 @@ public final class KBCommands {
}
// Update pending task
- pendingOperation = AbstractConfirmableOperation.createDeleteOperation(context, backupName);
+ //pendingOperation = AbstractConfirmableOperation.createDeleteOperation(context, backupName);
+ pendingOperation = new DeleteOperation(context, backupName);
msgWarn(context, String.format("DELETION WARNING: The deletion is irreversible! You will lose the backup %s permanently. Use /kb confirm to start or /kb cancel to abort.", backupName), true);
return SUCCESS;
@@ -161,7 +164,9 @@ public final class KBCommands {
}
// Update pending task
- pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName);
+ //pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName);
+ File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName));
+ pendingOperation = new RestoreOperation(context, backupFile.getAbsolutePath(), getLevelPath(server), backupName);
msgWarn(context, String.format("RESET WARNING: You will LOSE YOUR CURRENT WORLD PERMANENTLY! The worlds will be replaced with backup %s . Use /kb confirm to start or /kb cancel to abort.", backupName), true);
return SUCCESS;
@@ -195,8 +200,14 @@ public final class KBCommands {
// Do backup
BackupMetadata metadata = new BackupMetadata(System.currentTimeMillis(), backupName);
PrintUtil.info("Invoking backup worker ...");
- BackupWorker.invoke(context, backupName, metadata);
- return SUCCESS;
+ //BackupWorker.invoke(context, backupName, metadata);
+ BackupOperation operation = new BackupOperation(context, backupName, metadata);
+ if (operation.invoke()) {
+ return SUCCESS;
+ } else if (operation.isBlocked()) {
+ msgWarn(context, "Another task is running, cannot issue new backup at once.");
+ }
+ return FAILED;
}
/**
@@ -207,14 +218,14 @@ public final class KBCommands {
*/
public static int confirm(CommandContext<ServerCommandSource> context) {
if (pendingOperation == null) {
- msgWarn(context, "Nothing to be confirmed. Please execute /kb restore <backup_name> first.");
+ msgWarn(context, "Nothing to confirm.");
return FAILED;
}
- AbstractConfirmableOperation operation = pendingOperation;
+ Invokable operation = pendingOperation;
pendingOperation = null;
- boolean returnValue = operation.confirm();
+ boolean returnValue = operation.invoke();
// By the way, update suggestion list.
BackupNameSuggestionProvider.updateCandidateList();
diff --git a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
index 79105b2..dea79e7 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
@@ -59,7 +59,7 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback
// Print metadata
MetadataHolder.setMetadata(metadata);
- PrintUtil.info("Recovered from previous backup:");
+ PrintUtil.info("Restored from a previous backup:");
PrintUtil.info("Backup Name: " + metadata.getBackupName());
PrintUtil.info("Create Time: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date(metadata.getBackupTime())));
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java
deleted file mode 100644
index d666a34..0000000
--- a/src/main/java/com/keuin/kbackupfabric/operation/AbstractConfirmableOperation.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.keuin.kbackupfabric.operation;
-
-import com.mojang.brigadier.context.CommandContext;
-import net.minecraft.server.command.ServerCommandSource;
-
-public abstract class AbstractConfirmableOperation {
-
- public static AbstractConfirmableOperation createRestoreOperation(CommandContext<ServerCommandSource> context, String backupName) {
- return new RestoreOperation(context, backupName);
- }
-
- public static AbstractConfirmableOperation createDeleteOperation(CommandContext<ServerCommandSource> context, String backupName) {
- return new DeleteOperation(context, backupName);
- }
-
- public abstract boolean confirm();
-
- @Override
- public abstract String toString();
-}
diff --git a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
index d6db636..7d3a508 100644
--- a/src/main/java/com/keuin/kbackupfabric/worker/BackupWorker.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
@@ -1,6 +1,7 @@
-package com.keuin.kbackupfabric.worker;
+package com.keuin.kbackupfabric.operation;
import com.keuin.kbackupfabric.metadata.BackupMetadata;
+import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.util.ZipUtil;
import com.keuin.kbackupfabric.util.ZipUtilException;
@@ -17,56 +18,24 @@ import java.util.Map;
import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*;
import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo;
-/**
- * The backup worker
- * To invoke this worker, simply call invoke() method.
- */
-public final class BackupWorker implements Runnable {
+public class BackupOperation extends InvokableAsyncBlockingOperation {
- //private static final Logger LOGGER = LogManager.getLogger();
private final CommandContext<ServerCommandSource> context;
private final String backupName;
- private final Map<World, Boolean> oldWorldsSavingDisabled;
+ private final Map<World, Boolean> oldWorldsSavingDisabled = new HashMap<>();
private final BackupMetadata backupMetadata;
- private final long startTime;
+ private long startTime;
- private BackupWorker(CommandContext<ServerCommandSource> context, String backupName, Map<World, Boolean> oldWorldsSavingDisabled, BackupMetadata backupMetadata, long startTime) {
+
+ public BackupOperation(CommandContext<ServerCommandSource> context, String backupName, BackupMetadata backupMetadata) {
+ super("BackupWorker");
this.context = context;
this.backupName = backupName;
- this.oldWorldsSavingDisabled = oldWorldsSavingDisabled;
this.backupMetadata = backupMetadata;
- this.startTime = startTime;
- }
-
- public static void invoke(CommandContext<ServerCommandSource> context, String backupName, BackupMetadata backupMetadata) {
- //// Save world, save old autosave configs
-
- PrintUtil.broadcast(String.format("Making backup %s, please wait ...", backupName));
- 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
- PrintUtil.msgInfo(context, "Saving players ...");
- server.getPlayerManager().saveAllPlayerData();
- PrintUtil.msgInfo(context, "Saving worlds ...");
- server.save(true, true, true);
-
- // Start threaded worker
- BackupWorker worker = new BackupWorker(context, backupName, oldWorldsSavingDisabled, backupMetadata, System.currentTimeMillis());
- Thread workerThread = new Thread(worker, "BackupWorker");
- workerThread.start();
}
@Override
- public void run() {
+ protected void async() {
String backupSaveDirectory = "";
MinecraftServer server = context.getSource().getMinecraftServer();
try {
@@ -106,4 +75,31 @@ public final class BackupWorker implements Runnable {
msgInfo(context, "Failed to make zip: " + e.getMessage());
}
}
+
+ @Override
+ protected boolean sync() {
+ //// Save world, save old autosave configs
+
+ PrintUtil.broadcast(String.format("Making backup %s, please wait ...", backupName));
+
+ // Get server
+ MinecraftServer server = context.getSource().getMinecraftServer();
+
+ // Save old autosave switch stat temporally
+ oldWorldsSavingDisabled.clear();
+ server.getWorlds().forEach(world -> {
+ oldWorldsSavingDisabled.put(world, world.savingDisabled);
+ world.savingDisabled = true;
+ });
+
+ // Force to save all player data and worlds
+ PrintUtil.msgInfo(context, "Saving players ...");
+ server.getPlayerManager().saveAllPlayerData();
+ PrintUtil.msgInfo(context, "Saving worlds ...");
+ server.save(true, true, true);
+
+ // Log start time
+ startTime = System.currentTimeMillis();
+ return true;
+ }
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
index be7734d..ee1a57e 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
@@ -1,5 +1,7 @@
package com.keuin.kbackupfabric.operation;
+import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation;
+import com.keuin.kbackupfabric.util.BackupNameSuggestionProvider;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.MinecraftServer;
@@ -14,19 +16,30 @@ import static com.keuin.kbackupfabric.util.PrintUtil.msgErr;
import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo;
import static org.apache.commons.io.FileUtils.forceDelete;
-class DeleteOperation extends AbstractConfirmableOperation {
+public class DeleteOperation extends InvokableAsyncBlockingOperation {
//private static final Logger LOGGER = LogManager.getLogger();
private final String backupName;
private final CommandContext<ServerCommandSource> context;
- DeleteOperation(CommandContext<ServerCommandSource> context, String backupName) {
+ public DeleteOperation(CommandContext<ServerCommandSource> context, String backupName) {
+ super("BackupDeletingWorker");
this.backupName = backupName;
this.context = context;
}
@Override
- public boolean confirm() {
+ public String toString() {
+ return String.format("deletion of %s", backupName);
+ }
+
+ @Override
+ protected void async() {
+ delete();
+ BackupNameSuggestionProvider.updateCandidateList();
+ }
+
+ private void delete() {
MinecraftServer server = context.getSource().getMinecraftServer();
String backupFileName = getBackupFileName(backupName);
PrintUtil.info("Deleting backup " + backupName);
@@ -37,7 +50,7 @@ class DeleteOperation extends AbstractConfirmableOperation {
String msg = "Failed to delete file " + backupFileName;
PrintUtil.error(msg);
msgErr(context, msg);
- return false;
+ return;
}
try {
if (!backupFile.delete())
@@ -48,11 +61,5 @@ class DeleteOperation extends AbstractConfirmableOperation {
} while (backupFile.exists());
PrintUtil.info("Deleted backup " + backupName);
msgInfo(context, "Deleted backup " + backupName);
- return true;
- }
-
- @Override
- public String toString() {
- return String.format("deletion of %s", backupName);
}
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
index 4b129d9..cc19627 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
@@ -1,37 +1,49 @@
package com.keuin.kbackupfabric.operation;
+import com.keuin.kbackupfabric.operation.abstracts.InvokableBlockingOperation;
import com.keuin.kbackupfabric.util.PrintUtil;
-import com.keuin.kbackupfabric.worker.RestoreWorker;
+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 java.io.File;
+import java.io.IOException;
-import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*;
+import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.getBackupFileName;
+import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.getBackupSaveDirectory;
+import static org.apache.commons.io.FileUtils.forceDelete;
-class RestoreOperation extends AbstractConfirmableOperation {
+public class RestoreOperation extends InvokableBlockingOperation {
//private static final Logger LOGGER = LogManager.getLogger();
private final String backupName;
+ private final Thread serverThread;
+ private final String backupFilePath;
+ private final String levelDirectory;
private final CommandContext<ServerCommandSource> context;
+ private final MinecraftServer server;
- RestoreOperation(CommandContext<ServerCommandSource> context, String backupName) {
+ public RestoreOperation(CommandContext<ServerCommandSource> context, String backupFilePath, String levelDirectory, String backupName) {
+ server = context.getSource().getMinecraftServer();
this.backupName = backupName;
+ this.serverThread = server.getThread();
+ this.backupFilePath = backupFilePath;
+ this.levelDirectory = levelDirectory;
this.context = context;
}
@Override
- public boolean confirm() {
+ protected boolean blockingContext() {
// do restore to backupName
- MinecraftServer server = context.getSource().getMinecraftServer();
PrintUtil.broadcast(String.format("Restoring to previous world %s ...", backupName));
String backupFileName = getBackupFileName(backupName);
PrintUtil.debug("Backup file name: " + backupFileName);
File backupFile = new File(getBackupSaveDirectory(server), backupFileName);
- PrintUtil.msgInfo(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);
+ PrintUtil.msgInfo(context, "Server will shutdown in a few seconds, depending on world size and disk speed, the progress may take from seconds to minutes.", true);
PrintUtil.msgInfo(context, "Please do not force the server stop, or the level would be broken.", true);
PrintUtil.msgInfo(context, "After it shuts down, please restart the server manually.", true);
final int WAIT_SECONDS = 10;
@@ -42,7 +54,14 @@ class RestoreOperation extends AbstractConfirmableOperation {
}
}
PrintUtil.broadcast("Shutting down ...");
- RestoreWorker.invoke(server, backupFile.getPath(), getLevelPath(server));
+ //RestoreWorker worker = new RestoreWorker(server.getThread(), backupFilePath, levelDirectory);
+ Thread workerThread = new Thread(new WorkerThread(), "RestoreWorker");
+ workerThread.start();
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ignored) {
+ }
+ server.stop(false);
return true;
}
@@ -50,4 +69,61 @@ class RestoreOperation extends AbstractConfirmableOperation {
public String toString() {
return String.format("restoration from %s", backupName);
}
+
+ private class WorkerThread implements Runnable {
+
+ @Override
+ public void run() {
+ try {
+ // Wait server thread die
+ PrintUtil.info("Waiting for the server thread to exit ...");
+ while (serverThread.isAlive()) {
+ try {
+ serverThread.join();
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ PrintUtil.info("Wait for 5 seconds ...");
+ try {
+ Thread.sleep(5000);
+ } catch (InterruptedException ignored) {
+ }
+
+ // Delete old level
+ PrintUtil.info("Server stopped. Deleting old level ...");
+ File levelDirFile = new File(levelDirectory);
+ long startTime = System.currentTimeMillis();
+
+ 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()) {
+ PrintUtil.error(String.format("Cannot restore: failed to delete old level %s .", levelDirFile.getName()));
+ return;
+ }
+
+ // Decompress archive
+ PrintUtil.info("Decompressing archived level ...");
+ ZipUtil.unzip(backupFilePath, levelDirectory, false);
+ long endTime = System.currentTimeMillis();
+ PrintUtil.info(String.format("Restore complete! (%.2fs) Please restart the server manually.", (endTime - startTime) / 1000.0));
+ } catch (SecurityException | IOException | ZipUtilException e) {
+ PrintUtil.error("An exception occurred while restoring: " + e.getMessage());
+ }
+ }
+ }
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java
new file mode 100644
index 0000000..f1a19de
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractAsyncOperation.java
@@ -0,0 +1,53 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+public abstract class AbstractAsyncOperation extends AbstractSerializedOperation {
+
+ private final Thread thread;
+ private final String name;
+ private final Object sync = new Object();
+
+ protected AbstractAsyncOperation(String name) {
+ this.name = name;
+ this.thread = new Thread(this::async, name);
+ }
+
+ /**
+ * Start the worker thread.
+ *
+ * @return true if succeed starting, false if already started.
+ */
+ @Override
+ protected final boolean operate() {
+ synchronized (sync) {
+ if (thread.isAlive())
+ return false;
+ if (!sync())
+ return false;
+ thread.start();
+ return true;
+ }
+ }
+
+ /**
+ * Implement your async operation here.
+ * When this method returns, the operation must finish.
+ */
+ protected abstract void async();
+
+ /**
+ * If necessary, implement your sync operations here.
+ * It will be invoked before starting the async thread.
+ */
+ protected boolean sync() {
+ return true;
+ }
+
+ public final String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "operation " + name;
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java
new file mode 100644
index 0000000..de672cd
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractBlockingOperation.java
@@ -0,0 +1,58 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+import com.keuin.kbackupfabric.operation.abstracts.i.Blocking;
+
+public abstract class AbstractBlockingOperation extends AbstractSerializedOperation implements Blocking {
+
+ private static final Object sync = new Object();
+ private static boolean isBlocking = false;
+ private boolean wasBlocked = false;
+ private boolean noUnblocking = false;
+
+ @Override
+ protected final boolean operate() {
+ synchronized (sync) {
+ if (isBlocking) {
+ System.out.println("blocked.");
+ wasBlocked = true;
+ return false;
+ } else {
+ System.out.println("not blocked.");
+ wasBlocked = false;
+ isBlocking = true;
+ }
+ }
+ boolean exitCode = blockingContext();
+ if (!noUnblocking)
+ isBlocking = false;
+ return exitCode;
+ }
+
+ public final boolean isBlocked() {
+ return wasBlocked;
+ }
+
+ protected final void block(boolean blockState) {
+ isBlocking = blockState;
+ }
+
+ protected void noUnblocking(boolean b) {
+ noUnblocking = b;
+ }
+
+ protected boolean blockAndGet() {
+ synchronized (sync) {
+ boolean b = isBlocking;
+ isBlocking = true;
+ return b;
+ }
+ }
+
+ /**
+ * Implement your blocked operation here.
+ *
+ * @return the stat code.
+ */
+ protected abstract boolean blockingContext();
+
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java
new file mode 100644
index 0000000..0a56008
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractConfirmableOperation.java
@@ -0,0 +1,11 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+public abstract class AbstractConfirmableOperation extends AbstractSerializedOperation {
+
+ public final boolean confirm() {
+ return operate();
+ }
+
+ @Override
+ public abstract String toString();
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java
new file mode 100644
index 0000000..3168e8c
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/AbstractSerializedOperation.java
@@ -0,0 +1,12 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+public abstract class AbstractSerializedOperation {
+ /**
+ * Do your operation here.
+ * This method is not designed to be public.
+ * When this method returns, the operation must have finished.
+ *
+ * @return the stat code.
+ */
+ protected abstract boolean operate();
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java
new file mode 100644
index 0000000..6268d5a
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncBlockingOperation.java
@@ -0,0 +1,66 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+import com.keuin.kbackupfabric.operation.abstracts.i.Blocking;
+import com.keuin.kbackupfabric.operation.abstracts.i.Invokable;
+
+public abstract class InvokableAsyncBlockingOperation implements Invokable, Blocking {
+
+ private final InvokableAsyncOperation asyncOperation;
+ private final HackedBlockingOperation blockingOperation;
+
+ public InvokableAsyncBlockingOperation(String name) {
+ asyncOperation = new InvokableAsyncOperation(name) {
+ @Override
+ protected void async() {
+ InvokableAsyncBlockingOperation.this.async();
+ // When the async operation finishes, unblock
+ blockingOperation.noUnblocking(false);
+ blockingOperation.block(false);
+ }
+
+ @Override
+ protected boolean sync() {
+ return InvokableAsyncBlockingOperation.this.sync();
+ }
+ };
+
+ blockingOperation = new HackedBlockingOperation();
+ }
+
+ @Override
+ public boolean invoke() {
+ return blockingOperation.invoke();
+ }
+
+ @Override
+ public boolean isBlocked() {
+ return blockingOperation.isBlocked();
+ }
+
+ protected abstract void async();
+
+ protected boolean sync() {
+ return true;
+ }
+
+ private class HackedBlockingOperation extends InvokableBlockingOperation {
+
+ @Override
+ protected boolean blockingContext() {
+
+ noUnblocking(true);
+ return asyncOperation.invoke();
+ }
+
+ @Override
+ public void noUnblocking(boolean b) {
+ super.noUnblocking(b);
+ }
+
+ @Override
+ public void block(boolean blockState) {
+ super.block(blockState);
+ }
+ }
+
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java
new file mode 100644
index 0000000..518f670
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableAsyncOperation.java
@@ -0,0 +1,32 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+public abstract class InvokableAsyncOperation extends InvokableOperation {
+
+ private final AbstractAsyncOperation asyncOperation;
+
+
+ public InvokableAsyncOperation(String name) {
+ asyncOperation = new AbstractAsyncOperation(name) {
+ @Override
+ protected void async() {
+ InvokableAsyncOperation.this.async();
+ }
+
+ @Override
+ protected boolean sync() {
+ return InvokableAsyncOperation.this.sync();
+ }
+ };
+ }
+
+ protected abstract void async();
+
+ protected boolean sync() {
+ return true;
+ }
+
+ @Override
+ protected boolean operate() {
+ return asyncOperation.operate();
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java
new file mode 100644
index 0000000..b1a3748
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableBlockingOperation.java
@@ -0,0 +1,39 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+import com.keuin.kbackupfabric.operation.abstracts.i.Blocking;
+
+public abstract class InvokableBlockingOperation extends InvokableOperation implements Blocking {
+
+ private final AbstractBlockingOperation operation = new AbstractBlockingOperation() {
+ @Override
+ protected boolean blockingContext() {
+ return InvokableBlockingOperation.this.blockingContext();
+ }
+ };
+
+ @Override
+ protected final boolean operate() {
+ return operation.operate();
+ }
+
+ /**
+ * Implement your blocked operation here.
+ *
+ * @return stat code.
+ */
+ protected abstract boolean blockingContext();
+
+ protected void block(boolean blockState) {
+ operation.block(blockState);
+ }
+
+ protected void noUnblocking(boolean b) {
+ operation.noUnblocking(b);
+ }
+
+ @Override
+ public final boolean isBlocked() {
+ return operation.isBlocked();
+ }
+
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java
new file mode 100644
index 0000000..47c1ca8
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/InvokableOperation.java
@@ -0,0 +1,9 @@
+package com.keuin.kbackupfabric.operation.abstracts;
+
+import com.keuin.kbackupfabric.operation.abstracts.i.Invokable;
+
+public abstract class InvokableOperation extends AbstractSerializedOperation implements Invokable {
+ public boolean invoke() {
+ return operate();
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java
new file mode 100644
index 0000000..ba1b003
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Blocking.java
@@ -0,0 +1,5 @@
+package com.keuin.kbackupfabric.operation.abstracts.i;
+
+public interface Blocking {
+ boolean isBlocked();
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java
new file mode 100644
index 0000000..74051e3
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/abstracts/i/Invokable.java
@@ -0,0 +1,5 @@
+package com.keuin.kbackupfabric.operation.abstracts.i;
+
+public interface Invokable {
+ boolean invoke();
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java b/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
deleted file mode 100644
index 58560a1..0000000
--- a/src/main/java/com/keuin/kbackupfabric/worker/RestoreWorker.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.keuin.kbackupfabric.worker;
-
-import com.keuin.kbackupfabric.util.PrintUtil;
-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 org.apache.commons.io.FileUtils.forceDelete;
-
-/**
- * The restore worker
- * To invoke this worker, simply call invoke() method.
- */
-public final class RestoreWorker implements Runnable {
-
- //private static final Logger LOGGER = LogManager.getLogger();
-
- 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
- PrintUtil.info("Waiting for the server thread to exit ...");
- while (serverThread.isAlive()) {
- try {
- serverThread.join();
- } catch (InterruptedException ignored) {
- }
- }
-
- PrintUtil.info("Wait for 5 seconds ...");
- try {
- Thread.sleep(5000);
- } catch (InterruptedException ignored) {
- }
-
- // Delete old level
- PrintUtil.info("Server stopped. Deleting old level ...");
- File levelDirFile = new File(levelDirectory);
- long startTime = System.currentTimeMillis();
-
- 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()) {
- PrintUtil.error(String.format("Cannot restore: failed to delete old level %s .", levelDirFile.getName()));
- return;
- }
-
- // Decompress archive
- PrintUtil.info("Decompressing archived level");
- ZipUtil.unzip(backupFilePath, levelDirectory, false);
- long endTime = System.currentTimeMillis();
- PrintUtil.info(String.format("Restore complete! (%.2fs) Please restart the server manually.", (endTime - startTime) / 1000.0));
- } catch (SecurityException | IOException | ZipUtilException e) {
- PrintUtil.error("An exception occurred while restoring: " + e.getMessage());
- }
- }
-}