summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommands.java83
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java (renamed from src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java)26
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java6
-rw-r--r--src/main/java/com/keuin/kbackupfabric/Random.java97
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java11
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java2
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java49
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java25
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupMethod.java62
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupUtil.java90
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java64
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java33
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/ServerRestartUtil.java58
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java4
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java22
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java3
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java3
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java5
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java5
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/suggestion/BackupMethodSuggestionProvider.java36
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/suggestion/BackupNameSuggestionProvider.java (renamed from src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameSuggestionProvider.java)3
21 files changed, 430 insertions, 257 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
index 9568842..bc04291 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
@@ -1,14 +1,16 @@
package com.keuin.kbackupfabric;
-import com.keuin.kbackupfabric.metadata.BackupMetadata;
import com.keuin.kbackupfabric.metadata.MetadataHolder;
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.operation.backup.BackupMethod;
+import com.keuin.kbackupfabric.operation.backup.IncrementalBackupMethod;
+import com.keuin.kbackupfabric.operation.backup.PrimitiveBackupMethod;
import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil;
-import com.keuin.kbackupfabric.util.backup.BackupNameSuggestionProvider;
+import com.keuin.kbackupfabric.util.backup.BackupType;
+import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider;
import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.mojang.brigadier.arguments.StringArgumentType;
@@ -27,11 +29,13 @@ public final class KBCommands {
private static final int SUCCESS = 1;
private static final int FAILED = -1;
+ private static final String DEFAULT_BACKUP_NAME = "noname";
//private static final Logger LOGGER = LogManager.getLogger();
private static final List<String> backupNameList = new ArrayList<>(); // index -> backupName
private static Invokable pendingOperation = null;
+ //private static BackupMethod activatedBackupMethod = new PrimitiveBackupMethod(); // The backup method we currently using
/**
* Print the help menu.
@@ -41,12 +45,12 @@ public final class KBCommands {
*/
public static int help(CommandContext<ServerCommandSource> context) {
msgInfo(context, "==== KBackup Manual ====");
- msgInfo(context, "/kb /kb help Print help menu.");
- msgInfo(context, "/kb list Show all backups.");
- msgInfo(context, "/kb backup [backup_name] Backup the whole level to backup_name. The default name is current system time.");
- msgInfo(context, "/kb restore <backup_name> Delete the whole current level and restore from given backup. /kb restore is identical with /kb list.");
- msgInfo(context, "/kb confirm Confirm and start restoring.");
- msgInfo(context, "/kb cancel Cancel the restoration to be confirmed. If cancelled, /kb confirm will not run.");
+ msgInfo(context, "/kb , /kb help - Print help menu.");
+ msgInfo(context, "/kb list - Show all backups.");
+ msgInfo(context, "/kb backup [incremental/zip] [backup_name] - Backup the whole level to backup_name. The default name is current system time.");
+ msgInfo(context, "/kb restore <backup_name> - Delete the whole current level and restore from given backup. /kb restore is identical with /kb list.");
+ msgInfo(context, "/kb confirm - Confirm and start restoring.");
+ msgInfo(context, "/kb cancel - Cancel the restoration to be confirmed. If cancelled, /kb confirm will not run.");
return SUCCESS;
}
@@ -103,7 +107,7 @@ public final class KBCommands {
* @param context the context.
* @return stat code.
*/
- public static int backup(CommandContext<ServerCommandSource> context) {
+ public static int primitiveBackup(CommandContext<ServerCommandSource> context) {
//KBMain.backup("name")
String backupName = StringArgumentType.getString(context, "backupName");
if (backupName.matches("[0-9]*")) {
@@ -111,7 +115,32 @@ public final class KBCommands {
backupName = String.format("a%s", backupName);
msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", backupName));
}
- return doBackup(context, backupName, BackupMethod);
+ return doBackup(context, backupName, PrimitiveBackupMethod.getInstance());
+ }
+
+ /**
+ * Backup with default name.
+ *
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int primitiveBackupWithDefaultName(CommandContext<ServerCommandSource> context) {
+ return doBackup(context, DEFAULT_BACKUP_NAME, PrimitiveBackupMethod.getInstance());
+ }
+
+ public static int incrementalBackup(CommandContext<ServerCommandSource> context) {
+ //KBMain.backup("name")
+ String backupName = StringArgumentType.getString(context, "backupName");
+ if (backupName.matches("[0-9]*")) {
+ // Numeric param is not allowed
+ backupName = String.format("a%s", backupName);
+ msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", backupName));
+ }
+ return doBackup(context, backupName, IncrementalBackupMethod.getInstance());
+ }
+
+ public static int incrementalBackupWithDefaultName(CommandContext<ServerCommandSource> context) {
+ return doBackup(context, DEFAULT_BACKUP_NAME, IncrementalBackupMethod.getInstance());
}
/**
@@ -168,6 +197,9 @@ public final class KBCommands {
return FAILED;
}
+ // Detect backup type
+
+
// Update pending task
//pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName);
File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName));
@@ -177,17 +209,6 @@ public final class KBCommands {
return SUCCESS;
}
-
- /**
- * Backup with default name.
- *
- * @param context the context.
- * @return stat code.
- */
- public static int backupWithDefaultName(CommandContext<ServerCommandSource> context) {
- return doBackup(context, "noname");
- }
-
private static int doBackup(CommandContext<ServerCommandSource> context, String customBackupName, BackupMethod backupMethod) {
// Real backup name (compatible with legacy backup): date_name, such as 2020-04-23_21-03-00_test
//KBMain.backup("name")
@@ -287,6 +308,26 @@ public final class KBCommands {
return SUCCESS;
}
+ /**
+ * Select the backup method we use.
+ * @param context the context.
+ * @return stat code.
+ */
+ public static int setMethod(CommandContext<ServerCommandSource> context) {
+ String desiredMethodName = StringArgumentType.getString(context, "backupMethod");
+ List<BackupType> backupMethods = Arrays.asList(BackupType.PRIMITIVE_ZIP_BACKUP, BackupType.OBJECT_TREE_BACKUP);
+ for (BackupType method : backupMethods) {
+ if(method.getName().equals(desiredMethodName)) {
+ // Incremental backup
+// activatedBackupMethod =
+ msgInfo(context, String.format("Backup method is set to: %s", desiredMethodName));
+ return SUCCESS;
+ }
+ }
+
+ return SUCCESS;
+ }
+
private static String parseBackupName(CommandContext<ServerCommandSource> context, String userInput) {
try {
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java b/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java
index bfbe3c1..3376ac9 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommandRegister.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java
@@ -1,13 +1,14 @@
package com.keuin.kbackupfabric;
-import com.keuin.kbackupfabric.util.backup.BackupNameSuggestionProvider;
+import com.keuin.kbackupfabric.util.backup.suggestion.BackupMethodSuggestionProvider;
+import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider;
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 final class KBCommandRegister {
+public final class KBCommandsRegister {
// First make method to register
public static void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher) {
@@ -18,8 +19,24 @@ public final class KBCommandRegister {
// register /kb list for showing the backup list. OP is required.
dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("list").requires(PermissionValidator::op).executes(KBCommands::list)));
+ // register /kb backup zip [name] as a alias
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("backup").then(
+ CommandManager.literal("zip").then(
+ CommandManager.argument("backupName", StringArgumentType.greedyString()).requires(PermissionValidator::op).executes(KBCommands::primitiveBackup)
+ ).requires(PermissionValidator::op).executes(KBCommands::primitiveBackupWithDefaultName)))
+ );
+
+ // register /kb backup incremental [name]
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("backup").then(
+ CommandManager.literal("incremental").then(
+ CommandManager.argument("backupName", StringArgumentType.greedyString()).requires(PermissionValidator::op).executes(KBCommands::incrementalBackup)
+ ).requires(PermissionValidator::op).executes(KBCommands::incrementalBackupWithDefaultName)))
+ );
+
// register /kb backup [name] for performing backup. OP is required.
- 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)));
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("backup").then(
+ CommandManager.argument("backupName", StringArgumentType.greedyString()).requires(PermissionValidator::op).executes(KBCommands::primitiveBackup)
+ ).requires(PermissionValidator::op).executes(KBCommands::primitiveBackupWithDefaultName)));
// 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.getProvider()).requires(PermissionValidator::op).executes(KBCommands::restore)).executes(KBCommands::list)));
@@ -35,5 +52,8 @@ public final class KBCommandRegister {
// register /kb prev for showing the latest backup.
dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("prev").requires(PermissionValidator::op).executes(KBCommands::prev)));
+
+ // register /kb setMethod for selecting backup method (zip, incremental)
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("setMethod").then(CommandManager.argument("backupMethod", StringArgumentType.string()).suggests(BackupMethodSuggestionProvider.getProvider()).requires(PermissionValidator::op).executes(KBCommands::setMethod))));
}
}
diff --git a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
index d67d85b..3cb2dbe 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java
@@ -3,7 +3,7 @@ package com.keuin.kbackupfabric;
import com.keuin.kbackupfabric.metadata.BackupMetadata;
import com.keuin.kbackupfabric.metadata.MetadataHolder;
import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil;
-import com.keuin.kbackupfabric.util.backup.BackupNameSuggestionProvider;
+import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider;
import com.keuin.kbackupfabric.util.PrintUtil;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.server.ServerStartCallback;
@@ -30,7 +30,7 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback
@Override
public void onInitialize() {
System.out.println("Binding events and commands ...");
- CommandRegistry.INSTANCE.register(false, KBCommandRegister::registerCommands);
+ CommandRegistry.INSTANCE.register(false, KBCommandsRegister::registerCommands);
ServerStartCallback.EVENT.register(this);
}
@@ -58,7 +58,7 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback
// Print metadata
MetadataHolder.setMetadata(metadata);
- PrintUtil.info("Restored from a previous backup:");
+ PrintUtil.info("Restored world 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/Random.java b/src/main/java/com/keuin/kbackupfabric/Random.java
deleted file mode 100644
index e87e4c1..0000000
--- a/src/main/java/com/keuin/kbackupfabric/Random.java
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.keuin.kbackupfabric;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
-public class Random {
- public int random(int A, int B) {
- return (int) (A + (B - A + 1) * Math.random());
- }
-
- public double[] randomArray(int MaxN, int lowA, int highA) {
- double[] array = new double[random(1,MaxN)];
- for (int i = 0; i < array.length; i++) {
- array[i] = lowA + (highA - lowA) * Math.random();
- }
- return array;
- }
-
- public double[] generate(int MaxN, int lowA, int highA) {
- double[] testData = randomArray(MaxN,lowA,highA);
- double x = testData[random(0,testData.length-1)];
- }
-
- public int R(double[] a, double x) {
- List<Integer> list = new LinkedList<>();
- int k;
- for (int i = 0; i < a.length; i++) {
- if(Math.abs(a[i] - x) < 1e-6)
- list.add(i);
- }
- assert !list.isEmpty();
- k = list.get(list.size() / 2);
- return k;
- }
-
-
- public boolean domR(double[] a, double x) {
- for (double v : a) {
- if(Math.abs(v - x) < 1e-6)
- return true;
- }
- return false;
- }
-
- public boolean oracle(double[] a, double x, int k) {
- if(!(Math.abs(a[k] - x) < 1e-6))
- return false;
- int c1=0,c2=0;
- for (int i = 0; i < k; i++) {
- if(Math.abs(a[i] - x) < 1e-6)
- ++c1;
- }
- for (int i = k; i < a.length; i++) {
- if(Math.abs(a[i] - x) < 1e-6)
- ++c2;
- }
- return c1 - c2 >= -1 && c1 - c2 <= 1;
- }
-
- public boolean driver() {
- int MaxN = 80, lowA = 20, highA = 50;
- double[] a = randomArray(MaxN,lowA,highA);
- double x = a[random(0,a.length-1)];
- return oracle(a,x,search(a,x));
- }
-
-
- public int search(double[] a, double x) {
- int counter = 0;
- for (double i : a) {
- if(Math.abs(i - x) < 1e-6)
- ++counter;
- }
- counter /= 2;
- for (int i = 0; i < a.length; i++) {
- if(Math.abs(a[i] - x) < 1e-6) {
- --counter;
- if(counter == 0)
- return i;
- }
- }
- return -1;
- }
-
- public static void Main() {
- int pass = 0, fail = 0;
- for (int i = 0; i < 10000; i++) {
- if(driver())
- pass++;
- else
- fail++;
- }
- System.out.println(String.format("pass: %d, fail: %d.",pass,fail));
- }
-
-}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
index 071726b..d03c347 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
@@ -8,7 +8,6 @@ import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.util.backup.builder.BackupFileNameBuilder;
import com.keuin.kbackupfabric.util.backup.formatter.BackupFileNameFormatter;
import com.mojang.brigadier.context.CommandContext;
-import com.sun.istack.internal.NotNull;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.world.World;
@@ -30,7 +29,7 @@ public class BackupOperation extends InvokableAsyncBlockingOperation {
private long startTime;
- public BackupOperation(@NotNull CommandContext<ServerCommandSource> context, @NotNull String backupName, @NotNull BackupMethod backupMethod, @NotNull BackupFileNameBuilder backupFileNameBuilder, @NotNull BackupFileNameFormatter backupFileNameFormatter) {
+ public BackupOperation(CommandContext<ServerCommandSource> context, String backupName, BackupMethod backupMethod) {
super("BackupWorker");
this.context = context;
this.backupName = backupName;
@@ -56,7 +55,8 @@ public class BackupOperation extends InvokableAsyncBlockingOperation {
String levelPath = getLevelPath(server);
String backupFileName = getBackupFileName(backupName);
- if(backupMethod.backup(backupName,levelPath,backupSaveDirectory)) {
+ BackupMethod.BackupResult result = backupMethod.backup(backupName,levelPath,backupSaveDirectory);
+ if(result.isSuccess()) {
// Restore old autosave switch stat
server.getWorlds().forEach(world -> world.savingDisabled = oldWorldsSavingDisabled.getOrDefault(world, true));
@@ -64,10 +64,7 @@ public class BackupOperation extends InvokableAsyncBlockingOperation {
long timeElapsedMillis = System.currentTimeMillis() - startTime;
String msgText = String.format("Backup finished. Time elapsed: %.2fs.", timeElapsedMillis / 1000.0);
File backupZipFile = new File(backupSaveDirectory, backupFileName);
- try {
- msgText += String.format(" File size: %s.", humanFileSize(backupZipFile.length()));
- } catch (SecurityException ignored) {
- }
+ msgText += String.format(" File size: %s.", humanFileSize(result.getBackupSizeBytes()));
PrintUtil.msgInfo(context, msgText, true);
} else {
// failed
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
index 30fdfc0..444ca9a 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
@@ -1,7 +1,7 @@
package com.keuin.kbackupfabric.operation;
import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation;
-import com.keuin.kbackupfabric.util.backup.BackupNameSuggestionProvider;
+import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.MinecraftServer;
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
index ef6ab2b..22397a1 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
@@ -84,54 +84,21 @@ public class RestoreOperation extends InvokableBlockingOperation {
}
}
- 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);
+ int cnt = 5;
+ do {
+ PrintUtil.info(String.format("Wait %d seconds ...", cnt));
+ try{
+ Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
- }
- if (levelDirFile.exists()) {
- PrintUtil.error(String.format("Cannot restore: failed to delete old level %s .", levelDirFile.getName()));
- return;
- }
+ }while(--cnt > 0);
- // 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));
- PrintUtil.info("If you want to restart automatically after restoring, please visit the project manual at: https://github.com/keuin/KBackup-Fabric/blob/master/README.md");
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException ignored) {
- }
+ ////////////////////
//ServerRestartUtil.forkAndRestart();
System.exit(111);
- } catch (SecurityException | IOException | ZipUtilException e) {
+ } catch (SecurityException e) {
PrintUtil.error("An exception occurred while restoring: " + e.getMessage());
}
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java
index b0b77cb..4e9eb6c 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java
@@ -2,10 +2,12 @@ package com.keuin.kbackupfabric.operation.backup;
import com.keuin.kbackupfabric.util.backup.builder.BackupFileNameBuilder;
import com.keuin.kbackupfabric.util.backup.formatter.BackupFileNameFormatter;
-import com.sun.istack.internal.NotNull;
import java.io.IOException;
+/**
+ * Provide specific backup method, which is implemented statelessly.
+ */
public interface BackupMethod {
/**
@@ -14,10 +16,29 @@ public interface BackupMethod {
* @param backupName the backup name.
* @return if the backup operation succeed.
*/
- boolean backup(@NotNull String backupName, @NotNull String levelPath, @NotNull String backupSaveDirectory) throws IOException;
+ BackupResult backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException;
+
+ boolean restore(String backupName, String levelPath, String backupSaveDirectory) throws IOException;
BackupFileNameBuilder getBackupFileNameBuilder();
BackupFileNameFormatter getBackupFileNameFormatter();
+ class BackupResult {
+ private final boolean success;
+ private final long backupSizeBytes;
+
+ public BackupResult(boolean success, long backupSizeBytes) {
+ this.success = success;
+ this.backupSizeBytes = backupSizeBytes;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public long getBackupSizeBytes() {
+ return backupSizeBytes;
+ }
+ }
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupMethod.java
new file mode 100644
index 0000000..4a87bb3
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupMethod.java
@@ -0,0 +1,62 @@
+package com.keuin.kbackupfabric.operation.backup;
+
+import com.google.gson.JsonObject;
+import com.keuin.kbackupfabric.util.PrintUtil;
+import com.keuin.kbackupfabric.util.backup.builder.BackupFileNameBuilder;
+import com.keuin.kbackupfabric.util.backup.builder.ObjectTreeBackupFileNameBuilder;
+import com.keuin.kbackupfabric.util.backup.formatter.BackupFileNameFormatter;
+import com.keuin.kbackupfabric.util.backup.formatter.ObjectTreeBackupFileNameFormatter;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+
+public class IncrementalBackupMethod implements BackupMethod {
+
+ private static final IncrementalBackupMethod INSTANCE = new IncrementalBackupMethod();
+
+ public static IncrementalBackupMethod getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public BackupResult backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException {
+ /*
+ 1. Analyze the save directory, to get a json containing md5 values of all files.
+ 2. Copy new files which we do not have in our backup repository.
+ 3. Save the above json as a backup file. When restoring from this,
+ what we have to do is just copy all files back from the repository,
+ based on their md5 digests.
+ */
+
+ boolean success = true;
+ // Generate JSON
+ JsonObject hashJson = IncrementalBackupUtil.generateDirectoryJsonObject(levelPath);
+ // Copy files
+ long newFilesSizeBytes = IncrementalBackupUtil.saveNewFiles(backupSaveDirectory, levelPath, hashJson);
+ if(newFilesSizeBytes < 0) {
+ success = false;
+ PrintUtil.error("Failed to copy new files to object tree.");
+ }
+ // Save JSON tree
+ File jsonFile = new File(String.valueOf(Paths.get(backupSaveDirectory, BackupFileNameBuilder.objectTreeBackup().build(LocalDateTime.now(), backupName))));
+ // TODO
+ return new BackupResult(success, newFilesSizeBytes);
+ }
+
+ @Override
+ public boolean restore(String backupName, String levelPath, String backupSaveDirectory) throws IOException {
+ return false;
+ }
+
+ @Override
+ public BackupFileNameBuilder getBackupFileNameBuilder() {
+ return null;
+ }
+
+ @Override
+ public BackupFileNameFormatter getBackupFileNameFormatter() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupUtil.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupUtil.java
new file mode 100644
index 0000000..f90aef1
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupUtil.java
@@ -0,0 +1,90 @@
+package com.keuin.kbackupfabric.operation.backup;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.keuin.kbackupfabric.util.FilesystemUtil;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.*;
+import java.util.Map;
+
+public class IncrementalBackupUtil {
+ /**
+ * Generate a json object representing a directory and its all sub files and directories.
+ * @param path path to the directory.
+ * @return a json object.
+ */
+ public static JsonObject generateDirectoryJsonObject(String path) throws IOException {
+ JsonObject json = new JsonObject();
+ File directory = new File(path);
+ if (!(directory.isDirectory() && directory.exists()))
+ throw new IOException(String.format("Path %s is not a valid directory.", path));
+
+ // Iterate all sub files using BFS.
+ try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(path))) {
+ for (Path sub : directoryStream) {
+ if (sub.toFile().isFile()) {
+ // A sub file
+ // Just hash and add it as a string
+ try (InputStream is = Files.newInputStream(sub)) {
+ String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(is);
+ json.addProperty(sub.getFileName().toString(), md5);
+ }
+ } else {
+ // A sub directory
+ // Search into
+ json.addProperty(String.valueOf(sub.getFileName()), sub.toString());
+ }
+ }
+ }
+
+ return json;
+ }
+
+ /**
+ * Save new (or modified) files to target path, based on hash json.
+ * @param targetSavePath where we should save new files.
+ * @param sourcePath where new files come from. This path must be the base directory of given hash json.
+ * @param hashJson the json object obtained by calling generateDirectoryJsonObject method.
+ * @return total size of new files. If failed, will return -1.
+ */
+ public static long saveNewFiles(String targetSavePath, String sourcePath, JsonObject hashJson) throws IOException {
+ long bytesCopied = 0;
+ for (Map.Entry<String, JsonElement> entry : hashJson.entrySet()) {
+ String key = entry.getKey();
+ JsonElement value = entry.getValue();
+ if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) {
+ // A sub file
+ // key is file name
+ // value is file md5
+ String md5 = value.getAsJsonPrimitive().getAsString();
+ File saveTarget = new File(targetSavePath, md5);
+ if (!saveTarget.exists()) {
+ // Target file does not exist. We have to copy this to the target.
+ File sourceFile = new File(sourcePath, key);
+ Files.copy(sourceFile.toPath(), saveTarget.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
+ try {
+ bytesCopied += sourceFile.length();
+ } catch (SecurityException ignored) {
+ // failed to get the file size. Just ignore this.
+ }
+ }
+ } else if (value.isJsonObject()) {
+ // A sub directory
+ // key is directory name
+ // value is directory json object
+ // Go into
+ if(!value.isJsonObject())
+ throw new IllegalArgumentException(String.format("Hash json contains illegal argument of a directory item: %s -> %s.", key, value));
+ Path pathSource = Paths.get(sourcePath, key);
+ bytesCopied += saveNewFiles(targetSavePath, pathSource.toString(), value.getAsJsonObject());
+ } else {
+ throw new IllegalArgumentException(String.format("Hash json contains illegal element: %s -> %s.", key, value));
+ }
+ }
+ return bytesCopied;
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java
index bb1c8cb..854355d 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java
@@ -2,6 +2,7 @@ package com.keuin.kbackupfabric.operation.backup;
import com.keuin.kbackupfabric.exception.ZipUtilException;
import com.keuin.kbackupfabric.metadata.BackupMetadata;
+import com.keuin.kbackupfabric.util.FilesystemUtil;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.util.ZipUtil;
import com.keuin.kbackupfabric.util.backup.builder.BackupFileNameBuilder;
@@ -9,14 +10,23 @@ import com.keuin.kbackupfabric.util.backup.formatter.BackupFileNameFormatter;
import java.io.File;
import java.io.IOException;
-import java.time.LocalDate;
+import java.nio.file.Paths;
import java.time.LocalDateTime;
+import static org.apache.commons.io.FileUtils.forceDelete;
+
public class PrimitiveBackupMethod implements BackupMethod {
+
+ private static final PrimitiveBackupMethod INSTANCE = new PrimitiveBackupMethod();
+
+ public static PrimitiveBackupMethod getInstance() {
+ return INSTANCE;
+ }
+
@Override
- public boolean backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException {
+ public BackupResult backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException {
+ String backupFileName = BackupFileNameBuilder.primitiveZipBackup().build(LocalDateTime.now(),backupName);
try {
- String backupFileName = BackupFileNameBuilder.primitiveZipBackup().build(LocalDateTime.now(),backupName);
BackupMetadata backupMetadata = new BackupMetadata(System.currentTimeMillis(), backupName);
PrintUtil.info(String.format("zip(srcPath=%s, destPath=%s)", levelPath, backupSaveDirectory));
@@ -25,8 +35,54 @@ public class PrimitiveBackupMethod implements BackupMethod {
} catch (ZipUtilException exception) {
PrintUtil.info("Infinite recursive of directory tree detected, backup was aborted.");
+ return new BackupResult(false, 0);
+ }
+
+ // Get backup file size and return
+ return new BackupResult(true, FilesystemUtil.getFileSizeBytes(backupSaveDirectory, backupFileName));
+ }
+
+ @Override
+ public boolean restore(String backupName, String levelDirectory, String backupSaveDirectory) throws IOException {
+ // 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 false;
}
+
+ // TODO: Refactor this to the concrete BackupMethod.
+ // Decompress archive
+ PrintUtil.info("Decompressing archived level ...");
+ ZipUtil.unzip(Paths.get(backupSaveDirectory, backupName).toString(), levelDirectory, false);
+ long endTime = System.currentTimeMillis();
+ PrintUtil.info(String.format("Restore complete! (%.2fs) Please restart the server manually.", (endTime - startTime) / 1000.0));
+ PrintUtil.info("If you want to restart automatically after restoring, please visit the project manual at: https://github.com/keuin/KBackup-Fabric/blob/master/README.md");
+
+// try {
+// Thread.sleep(1000);
+// } catch (InterruptedException ignored) {
+// }
+
return true;
}
@@ -37,6 +93,6 @@ public class PrimitiveBackupMethod implements BackupMethod {
@Override
public BackupFileNameFormatter getBackupFileNameFormatter() {
- return BFNF;
+ return BackupFileNameFormatter.primitiveZipBackup();
}
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java b/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java
new file mode 100644
index 0000000..7f74725
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java
@@ -0,0 +1,33 @@
+package com.keuin.kbackupfabric.util;
+
+import java.io.File;
+
+public class FilesystemUtil {
+
+ /**
+ * Get file sizes in bytes.
+ * @param parentDirectory path to specific file.
+ * @param fileName file name.
+ * @return bytes. If failed, return -1.
+ */
+ public static long getFileSizeBytes(String parentDirectory, String fileName) {
+ long fileSize = -1;
+ try{
+ File backupZipFile = new File(parentDirectory, fileName);
+ fileSize = backupZipFile.length();
+ } catch (SecurityException ignored){
+ }
+ return fileSize;
+ }
+
+ public static long getFileSizeBytes(String filePath) {
+ long fileSize = -1;
+ try{
+ File backupZipFile = new File(filePath);
+ fileSize = backupZipFile.length();
+ } catch (SecurityException ignored){
+ }
+ return fileSize;
+ }
+
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/ServerRestartUtil.java b/src/main/java/com/keuin/kbackupfabric/util/ServerRestartUtil.java
deleted file mode 100644
index 1642e5c..0000000
--- a/src/main/java/com/keuin/kbackupfabric/util/ServerRestartUtil.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.keuin.kbackupfabric.util;
-
-public class ServerRestartUtil {
-
- public static void forkAndRestart() {
-// Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-// @Override
-// public void run() {
-// // Here we restart the minecraft server
-// StringBuilder cmd = new StringBuilder();
-// cmd.append(System.getProperty("java.home")).append(File.separator).append("bin").append(File.separator).append("java ");
-// for (String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
-// cmd.append(jvmArg + " ");
-// }
-// cmd.append("-cp ").append(ManagementFactory.getRuntimeMXBean().getClassPath()).append(" ");
-// cmd.append(MinecraftServer.class.getName()).append(" ");
-// for (String arg : args) {
-// cmd.append(arg).append(" ");
-// }
-// Runtime.getRuntime().exec(cmd.toString());
-// System.exit(0);
-// }
-// }));
- }
-
- private static void startRestartThread() {
-// (new Thread(() -> {
-//
-//
-// // kill threads
-// Set<Thread> threads = Thread.getAllStackTraces().keySet();
-// Thread currentThread = Thread.currentThread();
-// for (Thread t : threads) {
-// if(t != currentThread && t.isAlive()) {
-// t.setUncaughtExceptionHandler((t1, e) -> {
-// // set empty handler
-// });
-// //t.interrupt();
-// try {
-// t.setDaemon(true);
-// } catch (Exception ignored) {
-// }
-//
-// t.stop();
-// }
-// }
-//
-// try {
-// Thread.sleep(5000);
-// } catch (InterruptedException ignored) {
-// }
-//
-// // restart Minecraft server
-// String[] args = new String[]{};
-// MinecraftServer.main(args);
-// })).start();
- }
-}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
index 0ca8da3..c670cf1 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/ZipUtil.java
@@ -147,9 +147,9 @@ public final class ZipUtil {
* @param unzipFilePath 解压后的文件保存的路径
* @param includeZipFileName 解压后的文件保存的路径是否包含压缩文件的文件名。true-包含;false-不包含
*/
- public static void unzip(String zipFilePath, String unzipFilePath, boolean includeZipFileName) throws ZipUtilException, IOException {
+ public static void unzip(String zipFilePath, String unzipFilePath, boolean includeZipFileName) throws IOException {
if (zipFilePath.isEmpty() || unzipFilePath.isEmpty()) {
- throw new ZipUtilException("Parameter for unzip() contains null.");
+ throw new IllegalArgumentException("Parameter for unzip() contains null.");
}
File zipFile = new File(zipFilePath);
// 如果解压后的文件保存路径包含压缩文件的文件名,则追加该文件名到解压路径
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java
index 95a32ae..d02ce77 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java
@@ -4,18 +4,30 @@ package com.keuin.kbackupfabric.util.backup;
* Representing the backup type.
* Should only be used in BackupFileNameBuilder and BackupFileNameFormatter
*/
+@Deprecated
public enum BackupType {
- PRIMITIVE_ZIP_BACKUP("Primitive Zip Backup"),
- OBJECT_TREE_BACKUP("Object Tree Backup");
+ PRIMITIVE_ZIP_BACKUP("Primitive Zip Backup", "zip"),
+ OBJECT_TREE_BACKUP("Object Tree Backup", "incremental");
- private final String name;
- BackupType(String name) {
+ private final String friendlyName; // e.g. Primitive Zip Backup
+ private final String name; // e.g. zip
+
+ BackupType(String friendlyName, String name) {
+ this.friendlyName = friendlyName;
this.name = name;
}
+ /**
+ * Get name used in command.
+ * @return name (such as "zip", "incremental").
+ */
+ public String getName() {
+ return name;
+ }
+
@Override
public String toString() {
- return name;
+ return friendlyName;
}
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java
index 16c598b..f57302c 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java
@@ -1,6 +1,5 @@
package com.keuin.kbackupfabric.util.backup.builder;
-import com.sun.istack.internal.NotNull;
import java.time.LocalDateTime;
@@ -20,6 +19,6 @@ public interface BackupFileNameBuilder {
* @param backupName the custom name of this backup. Note that this should be a valid file name in current file system.
* @return the backup file name string.
*/
- String build(@NotNull LocalDateTime time, @NotNull String backupName);
+ String build(LocalDateTime time, String backupName);
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java
index eae3639..a437629 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java
@@ -1,7 +1,6 @@
package com.keuin.kbackupfabric.util.backup.formatter;
import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter;
-import com.sun.istack.internal.NotNull;
import java.time.LocalDateTime;
import java.util.regex.Matcher;
@@ -9,7 +8,7 @@ import java.util.regex.Pattern;
public interface BackupFileNameFormatter {
- BackupFileName format(@NotNull String fileName);
+ BackupFileName format(String fileName);
class BackupFileName {
public final LocalDateTime time;
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java
index e5503a9..08805b2 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java
@@ -2,8 +2,6 @@ package com.keuin.kbackupfabric.util.backup.formatter;
import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil;
import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter;
-import com.sun.istack.internal.NotNull;
-import com.sun.istack.internal.Nullable;
import org.spongepowered.asm.mixin.Overwrite;
import java.time.LocalDateTime;
@@ -19,13 +17,12 @@ public class ObjectTreeBackupFileNameFormatter implements BackupFileNameFormatte
}
@Override
- public BackupFileNameFormatter.BackupFileName format(@NotNull String fileName) {
+ public BackupFileNameFormatter.BackupFileName format(String fileName) {
LocalDateTime time = getTime(fileName);
String name = getBackupName(fileName);
return new BackupFileNameFormatter.BackupFileName(time,name);
}
- @Nullable
private LocalDateTime getTime(String fileName) {
Matcher matcher = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}").matcher(fileName);
if (matcher.find()) {
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java
index 40450eb..2d50d17 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java
@@ -2,8 +2,6 @@ package com.keuin.kbackupfabric.util.backup.formatter;
import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil;
import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter;
-import com.sun.istack.internal.NotNull;
-import com.sun.istack.internal.Nullable;
import java.time.LocalDateTime;
import java.util.regex.Matcher;
@@ -18,13 +16,12 @@ public class PrimitiveZipBackupFileNameFormatter implements BackupFileNameFormat
}
@Override
- public BackupFileNameFormatter.BackupFileName format(@NotNull String fileName) {
+ public BackupFileNameFormatter.BackupFileName format(String fileName) {
LocalDateTime time = getTime(fileName);
String name = getBackupName(fileName);
return new BackupFileNameFormatter.BackupFileName(time,name);
}
- @Nullable
private LocalDateTime getTime(String fileName) {
Matcher matcher = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}").matcher(fileName);
if (matcher.find()) {
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/suggestion/BackupMethodSuggestionProvider.java b/src/main/java/com/keuin/kbackupfabric/util/backup/suggestion/BackupMethodSuggestionProvider.java
new file mode 100644
index 0000000..320d9bf
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/suggestion/BackupMethodSuggestionProvider.java
@@ -0,0 +1,36 @@
+package com.keuin.kbackupfabric.util.backup.suggestion;
+
+import com.keuin.kbackupfabric.util.backup.BackupType;
+import com.mojang.brigadier.suggestion.SuggestionProvider;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import net.minecraft.server.command.ServerCommandSource;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+
+public class BackupMethodSuggestionProvider {
+
+ private static final List<String> suggestions = Arrays.asList(
+ BackupType.OBJECT_TREE_BACKUP.getName(),
+ BackupType.PRIMITIVE_ZIP_BACKUP.getName()
+ ); // All backup methods
+
+ public static SuggestionProvider<ServerCommandSource> getProvider() {
+ return (context, builder) -> getCompletableFuture(builder);
+ }
+
+ private static CompletableFuture<Suggestions> getCompletableFuture(SuggestionsBuilder builder) {
+ String remaining = builder.getRemaining().toLowerCase(Locale.ROOT);
+ for (String string : suggestions) { // Iterate through the supplied list
+ if (string.toLowerCase(Locale.ROOT).startsWith(remaining)) {
+ builder.suggest(string); // Add every single entry to suggestions list.
+ }
+ }
+ return builder.buildFuture(); // Create the CompletableFuture containing all the suggestions
+ }
+
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameSuggestionProvider.java b/src/main/java/com/keuin/kbackupfabric/util/backup/suggestion/BackupNameSuggestionProvider.java
index 4639e99..f6f4056 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameSuggestionProvider.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/suggestion/BackupNameSuggestionProvider.java
@@ -1,5 +1,6 @@
-package com.keuin.kbackupfabric.util.backup;
+package com.keuin.kbackupfabric.util.backup.suggestion;
+import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;