summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeuin <[email protected]>2020-04-24 08:48:13 +0800
committerkeuin <[email protected]>2020-04-24 08:48:13 +0800
commit87f5cd97ab22b17a948eb9af1b49a0b1e777048d (patch)
tree26513b679f0f5327fed4f7233731430a40993aa6
parent8f3951090a8b1f385d1e2ab1319497c23fdeb3d8 (diff)
Added /kb delete <backup_name> for deleting an backup (with auto-complete supported).
-rw-r--r--README.md4
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java5
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommands.java149
-rw-r--r--src/main/java/com/keuin/kbackupfabric/data/PendingOperation.java41
-rw-r--r--src/main/java/com/keuin/kbackupfabric/data/PendingOperationType.java6
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/BackupNameSuggestionProvider.java3
6 files changed, 166 insertions, 42 deletions
diff --git a/README.md b/README.md
index 5036942..9c498a4 100644
--- a/README.md
+++ b/README.md
@@ -13,11 +13,13 @@ commands:
- **/kb backup \[backup_name\]**: make a backup with given name or the current system time by default
- **/kb restore \<backup_name\>**: restore to a certain backup. This command needs a confirm to execute.
- **/kb confirm**: confirm executing restore operation. The operation is irreversible.
+- **/kb delete**: delete an existing backup.
To-Do List:
+- Optimize log output, normal output and op broadcast output.
- More thorough test.
-- Enhance ZipUtil for hashing sub-files and generating incremental diff-table (A:Add, M:Modification, D:Deletion)
+- Enhance ZipUtil for hashing sub-files and generating incremental diff-table. (A:Add, M:Modification, D:Deletion)
- Optimize help menu. (colored command help menu)
- Implement incremental backup.
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
index 775dd62..4b4dd59 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
@@ -22,7 +22,10 @@ public final class KBCommandRegister {
dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("backup").then(CommandManager.argument("backupName", StringArgumentType.greedyString()).requires(PermissionValidator::op).executes(KBCommands::backup)).requires(PermissionValidator::op).executes(KBCommands::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.greedyString()).suggests(BackupNameSuggestionProvider.getSuggestionProvider()).requires(PermissionValidator::op).executes(KBCommands::restore)).executes(KBCommands::list)));
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("restore").then(CommandManager.argument("backupName", StringArgumentType.greedyString()).suggests(BackupNameSuggestionProvider.getProvider()).requires(PermissionValidator::op).executes(KBCommands::restore)).executes(KBCommands::list)));
+
+ // register /kb delete [name] for deleting an existing backup. OP is required.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("delete").then(CommandManager.argument("backupName", StringArgumentType.greedyString()).suggests(BackupNameSuggestionProvider.getProvider()).requires(PermissionValidator::op).executes(KBCommands::delete))));
// register /kb confirm for confirming the execution. OP is required.
dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("confirm").requires(PermissionValidator::op).executes(KBCommands::confirm)));
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
index b93a5c9..be4d151 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
@@ -1,6 +1,7 @@
package com.keuin.kbackupfabric;
import com.keuin.kbackupfabric.data.BackupMetadata;
+import com.keuin.kbackupfabric.data.PendingOperation;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.worker.BackupWorker;
import com.keuin.kbackupfabric.worker.RestoreWorker;
@@ -12,12 +13,14 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
+import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import static com.keuin.kbackupfabric.util.BackupFilesystemUtil.*;
import static com.keuin.kbackupfabric.util.PrintUtil.*;
+import static org.apache.commons.io.FileUtils.forceDelete;
public final class KBCommands {
@@ -28,7 +31,7 @@ public final class KBCommands {
private static final Logger LOGGER = LogManager.getLogger();
private static final HashMap<Integer, String> backupIndexNameMapper = new HashMap<>(); // index -> backupName
- private static String restoreBackupNameToBeConfirmed = null;
+ private static PendingOperation pendingOperation = null;
/**
* Print the help menu.
@@ -84,6 +87,36 @@ public final class KBCommands {
}
/**
+ * Delete an existing backup with context parameter backupName.
+ * Simply set the pending backupName to given backupName, for the second confirmation.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int delete(CommandContext<ServerCommandSource> context) {
+
+ String backupName = parseBackupName(context, StringArgumentType.getString(context, "backupName"));
+ MinecraftServer server = context.getSource().getMinecraftServer();
+
+ if (backupName == null)
+ return list(context); // Show the list and return
+
+ // Validate backupName
+ if (!isBackupNameValid(backupName, server)) {
+ // Invalid backupName
+ msgErr(context, "Invalid backup name! Please check your input. The list index number is also valid.");
+ return FAILED;
+ }
+
+ // Update pending task
+ pendingOperation = PendingOperation.deleteOperation(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.", pendingOperation.getBackupName()), true);
+ return SUCCESS;
+ }
+
+
+ /**
* Restore with context parameter backupName.
* Simply set the pending backupName to given backupName, for the second confirmation.
*
@@ -93,17 +126,11 @@ public final class KBCommands {
public static int restore(CommandContext<ServerCommandSource> context) {
//KBMain.restore("name")
MinecraftServer server = context.getSource().getMinecraftServer();
- String backupName = StringArgumentType.getString(context, "backupName");
+ String backupName = parseBackupName(context, StringArgumentType.getString(context, "backupName"));
+ backupName = parseBackupName(context, backupName);
- if (backupName.matches("[0-9]*")) {
- // If numeric input
- Integer index = Integer.parseInt(backupName);
- String realBackupName = backupIndexNameMapper.get(index);
- if (realBackupName == null) {
- return list(context); // Show the list and return
- }
- backupName = realBackupName; // Replace input number with real backup name.
- }
+ if (backupName == null)
+ return list(context); // Show the list and return
// Validate backupName
if (!isBackupNameValid(backupName, server)) {
@@ -112,9 +139,10 @@ public final class KBCommands {
return FAILED;
}
- // Update confirm pending variable
- restoreBackupNameToBeConfirmed = backupName;
- msgWarn(context, String.format("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.", restoreBackupNameToBeConfirmed), true);
+ // Update pending task
+ pendingOperation = PendingOperation.restoreOperation(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.", pendingOperation.getBackupName()), true);
return SUCCESS;
}
@@ -159,34 +187,65 @@ public final class KBCommands {
* @return stat code.
*/
public static int confirm(CommandContext<ServerCommandSource> context) {
- if (restoreBackupNameToBeConfirmed == null) {
- msgInfo(context, "Nothing to be confirmed. Please execute /kb restore <backup_name> first.");
+ if (pendingOperation == null) {
+ msgWarn(context, "Nothing to be confirmed. Please execute /kb restore <backup_name> first.");
return FAILED;
}
- // do restore to backupName
- String backupName = restoreBackupNameToBeConfirmed;
- PrintUtil.msgInfo(context, String.format("Restoring to previous world %s ...", backupName), true);
-
- // Get server
MinecraftServer server = context.getSource().getMinecraftServer();
- String backupFileName = getBackupFileName(backupName);
- LOGGER.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, "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;
- for (int i = 0; i < WAIT_SECONDS; ++i) {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException ignored) {
+
+ // Restore
+ if (pendingOperation.isRestore()) {
+ // do restore to backupName
+ String backupName = pendingOperation.getBackupName();
+ PrintUtil.msgInfo(context, String.format("Restoring to previous world %s ...", backupName), true);
+
+ String backupFileName = getBackupFileName(backupName);
+ LOGGER.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, "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;
+ for (int i = 0; i < WAIT_SECONDS; ++i) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ignored) {
+ }
}
+ PrintUtil.msgInfo(context, "Shutting down ...", true);
+ RestoreWorker.invoke(server, backupFile.getPath(), getLevelPath(server));
+ return SUCCESS;
}
- PrintUtil.msgInfo(context, "Shutting down ...", true);
- RestoreWorker.invoke(server, backupFile.getPath(), getLevelPath(server));
- return SUCCESS;
+
+ // Delete
+ if (pendingOperation.isDelete()) {
+ String backupName = pendingOperation.getBackupName();
+ String backupFileName = getBackupFileName(backupName);
+ LOGGER.info("Deleting backup " + backupName);
+ File backupFile = new File(getBackupSaveDirectory(server), backupFileName);
+ int tryCounter = 0;
+ do {
+ if (tryCounter == 5) {
+ String msg = "Failed to delete file " + backupFileName;
+ LOGGER.error(msg);
+ msgErr(context, msg);
+ return FAILED;
+ }
+ try {
+ if (!backupFile.delete())
+ forceDelete(backupFile);
+ } catch (SecurityException | NullPointerException | IOException ignored) {
+ }
+ ++tryCounter;
+ } while (backupFile.exists());
+ LOGGER.info("Deleted backup " + backupName);
+ msgInfo(context, "Deleted backup " + backupName);
+ return SUCCESS;
+ }
+
+ return SUCCESS; // block compiler's complain.
}
/**
@@ -196,13 +255,25 @@ public final class KBCommands {
* @return stat code.
*/
public static int cancel(CommandContext<ServerCommandSource> context) {
- if (restoreBackupNameToBeConfirmed != null) {
- restoreBackupNameToBeConfirmed = null;
- PrintUtil.msgInfo(context, "The restoration is cancelled.", true);
+ if (pendingOperation != null) {
+ PrintUtil.msgInfo(context, String.format("The %s has been cancelled.", pendingOperation.toString()), true);
+ pendingOperation = null;
return SUCCESS;
} else {
msgErr(context, "Nothing to cancel.");
return FAILED;
}
}
+
+ private static String parseBackupName(CommandContext<ServerCommandSource> context, String userInput) {
+ MinecraftServer server = context.getSource().getMinecraftServer();
+ String backupName = StringArgumentType.getString(context, "backupName");
+
+ if (backupName.matches("[0-9]*")) {
+ // If numeric input
+ Integer index = Integer.parseInt(backupName);
+ return backupIndexNameMapper.get(index); // Replace input number with real backup name.
+ }
+ return userInput;
+ }
}
diff --git a/src/main/java/com/keuin/kbackupfabric/data/PendingOperation.java b/src/main/java/com/keuin/kbackupfabric/data/PendingOperation.java
new file mode 100644
index 0000000..8f7490c
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/data/PendingOperation.java
@@ -0,0 +1,41 @@
+package com.keuin.kbackupfabric.data;
+
+public class PendingOperation {
+ private final PendingOperationType operationType;
+ private final String backupName;
+
+ public PendingOperation(PendingOperationType operationType, String backupName) {
+ this.operationType = operationType;
+ this.backupName = backupName;
+ }
+
+ public static PendingOperation deleteOperation(String backupName) {
+ return new PendingOperation(PendingOperationType.DELETE, backupName);
+ }
+
+ public static PendingOperation restoreOperation(String backupName) {
+ return new PendingOperation(PendingOperationType.RESTORE_TO, backupName);
+ }
+
+ public boolean isDelete() {
+ return operationType == PendingOperationType.DELETE;
+ }
+
+ public boolean isRestore() {
+ return operationType == PendingOperationType.RESTORE_TO;
+ }
+
+ public String getBackupName() {
+ return backupName;
+ }
+
+ @Override
+ public String toString() {
+ String op = "operation";
+ if (isDelete())
+ op = "deletion";
+ if (isRestore())
+ op = "restoration";
+ return String.format("%s on backup %s", op, getBackupName());
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/data/PendingOperationType.java b/src/main/java/com/keuin/kbackupfabric/data/PendingOperationType.java
new file mode 100644
index 0000000..f86fc0d
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/data/PendingOperationType.java
@@ -0,0 +1,6 @@
+package com.keuin.kbackupfabric.data;
+
+enum PendingOperationType {
+ RESTORE_TO,
+ DELETE
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/BackupNameSuggestionProvider.java b/src/main/java/com/keuin/kbackupfabric/util/BackupNameSuggestionProvider.java
index 08e12b2..7e87815 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/BackupNameSuggestionProvider.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/BackupNameSuggestionProvider.java
@@ -12,6 +12,7 @@ import java.util.Locale;
import java.util.concurrent.CompletableFuture;
public class BackupNameSuggestionProvider {
+
private static final List<String> candidateCacheList = new ArrayList<>();
private static final Object syncSetDirectory = new Object();
private static final Object syncUpdate = new Object();
@@ -48,7 +49,7 @@ public class BackupNameSuggestionProvider {
// candidateList.addAll(stringCollection);
// }
- public static SuggestionProvider<ServerCommandSource> getSuggestionProvider() {
+ public static SuggestionProvider<ServerCommandSource> getProvider() {
return (context, builder) -> getCompletableFuture(builder);
}