summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKeuin <[email protected]>2021-01-13 17:47:20 +0800
committerkeuin <[email protected]>2021-01-13 17:47:20 +0800
commite0c6a21fe9bfb01237fd145064f0af309879a9fb (patch)
tree321955f96030213c2bc8c3ec350961a81b60edee
parent2bc659d8f95a97d0514491e48ed9c66828a4e308 (diff)
Incremental backup now works (tested, but not thoroughly)
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommands.java44
-rw-r--r--src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java12
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java11
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java34
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredBackupMethod.java14
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethod.java66
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java42
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java38
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java9
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java15
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java43
-rw-r--r--src/main/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoder.java2
12 files changed, 248 insertions, 82 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
index 8f0e451..847fe7c 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java
@@ -5,9 +5,12 @@ 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.method.ConfiguredBackupMethod;
+import com.keuin.kbackupfabric.operation.backup.method.ConfiguredIncrementalBackupMethod;
import com.keuin.kbackupfabric.operation.backup.method.ConfiguredPrimitiveBackupMethod;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil;
+import com.keuin.kbackupfabric.util.backup.name.IncrementalBackupFileNameEncoder;
import com.keuin.kbackupfabric.util.backup.name.PrimitiveBackupFileNameEncoder;
import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider;
import com.mojang.brigadier.arguments.StringArgumentType;
@@ -120,7 +123,7 @@ public final class KBCommands {
customBackupName = String.format("a%s", customBackupName);
msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", customBackupName));
}
- return doBackup(context, customBackupName);
+ return doBackup(context, customBackupName, false);
}
/**
@@ -130,9 +133,24 @@ public final class KBCommands {
* @return stat code.
*/
public static int primitiveBackupWithDefaultName(CommandContext<ServerCommandSource> context) {
- return doBackup(context, DEFAULT_BACKUP_NAME);
+ return doBackup(context, DEFAULT_BACKUP_NAME, false);
}
+ public static int incrementalBackup(CommandContext<ServerCommandSource> context) {
+ String customBackupName = StringArgumentType.getString(context, "backupName");
+ if (customBackupName.matches("[0-9]*")) {
+ // Numeric param is not allowed
+ customBackupName = String.format("a%s", customBackupName);
+ msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", customBackupName));
+ }
+ return doBackup(context, customBackupName, true);
+ }
+
+ public static int incrementalBackupWithDefaultName(CommandContext<ServerCommandSource> context) {
+ return doBackup(context, DEFAULT_BACKUP_NAME, true);
+ }
+
+
// public static int incrementalBackup(CommandContext<ServerCommandSource> context) {
// //KBMain.backup("name")
// String backupName = StringArgumentType.getString(context, "backupName");
@@ -208,13 +226,24 @@ public final class KBCommands {
// Update pending task
//pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName);
// File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName));
- pendingOperation = new RestoreOperation(context, getBackupSaveDirectory(server).getAbsolutePath(), getLevelPath(server), backupFileName);
+ // TODO: improve this
+ ConfiguredBackupMethod method = backupFileName.endsWith(".zip") ?
+ new ConfiguredPrimitiveBackupMethod(
+ backupFileName, getLevelPath(server), getBackupSaveDirectory(server).getAbsolutePath()
+ ) : new ConfiguredIncrementalBackupMethod(
+ backupFileName, getLevelPath(server),
+ getBackupSaveDirectory(server).getAbsolutePath(),
+ getIncrementalBackupBaseDirectory(server).getAbsolutePath()
+ );
+ // String backupSavePath, String levelPath, String backupFileName
+// getBackupSaveDirectory(server).getAbsolutePath(), getLevelPath(server), backupFileName
+ pendingOperation = new RestoreOperation(context, method);
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.", backupFileName), true);
return SUCCESS;
}
- private static int doBackup(CommandContext<ServerCommandSource> context, String customBackupName) {
+ private static int doBackup(CommandContext<ServerCommandSource> context, String customBackupName, boolean incremental) {
// Real backup name (compatible with legacy backup): date_name, such as 2020-04-23_21-03-00_test
//KBMain.backup("name")
// String backupName = BackupNameTimeFormatter.getTimeString() + "_" + customBackupName;
@@ -232,10 +261,15 @@ public final class KBCommands {
// configure backup method
MinecraftServer server = context.getSource().getMinecraftServer();
- ConfiguredPrimitiveBackupMethod method = new ConfiguredPrimitiveBackupMethod(
+ ConfiguredBackupMethod method = !incremental ? new ConfiguredPrimitiveBackupMethod(
new PrimitiveBackupFileNameEncoder().encode(customBackupName, LocalDateTime.now()),
getLevelPath(server),
getBackupSaveDirectory(server).getAbsolutePath()
+ ) : new ConfiguredIncrementalBackupMethod(
+ new IncrementalBackupFileNameEncoder().encode(customBackupName, LocalDateTime.now()),
+ getLevelPath(server),
+ getBackupSaveDirectory(server).getAbsolutePath(),
+ getIncrementalBackupBaseDirectory(server).getAbsolutePath()
);
// dispatch to operation worker
diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java b/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java
index 4154464..d9ace66 100644
--- a/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java
+++ b/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java
@@ -18,18 +18,16 @@ public final class KBCommandsRegister {
// 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 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::primitiveBackup)
).requires(PermissionValidator::op).executes(KBCommands::primitiveBackupWithDefaultName)));
+ // register /kb incbak [name] for performing incremental backup. OP is required.
+ dispatcher.register(CommandManager.literal("kb").then(CommandManager.literal("incbak").then(
+ CommandManager.argument("backupName", StringArgumentType.greedyString()).requires(PermissionValidator::op).executes(KBCommands::incrementalBackup)
+ ).requires(PermissionValidator::op).executes(KBCommands::incrementalBackupWithDefaultName)));
+
// 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)));
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
index 645facd..b38921d 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
@@ -9,13 +9,11 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.world.World;
-import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo;
-import static com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil.getBackupSaveDirectory;
public class BackupOperation extends InvokableAsyncBlockingOperation {
@@ -39,15 +37,12 @@ public class BackupOperation extends InvokableAsyncBlockingOperation {
//// Do our main backup logic
// Create backup saving directory
- File backupSaveDirectoryFile = getBackupSaveDirectory(server);
- backupSaveDirectory = backupSaveDirectoryFile.getName();
- if (!backupSaveDirectoryFile.isDirectory() && !backupSaveDirectoryFile.mkdir()) {
- msgInfo(context, String.format("Failed to create backup saving directory: %s. Failed to backup.", backupSaveDirectory));
+ if (!configuredBackupMethod.touch()) {
+ PrintUtil.msgErr(context, "Failed to create backup save directory. Cannot backup.");
return;
}
- // Make zip
-
+ // Backup
BackupFeedback result = configuredBackupMethod.backup();
if (result.isSuccess()) {
// Restore old auto-save switch stat
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
index 011d2e2..b870746 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
@@ -2,37 +2,35 @@ package com.keuin.kbackupfabric.operation;
import com.keuin.kbackupfabric.operation.abstracts.InvokableBlockingOperation;
import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod;
-import com.keuin.kbackupfabric.operation.backup.method.ConfiguredPrimitiveBackupMethod;
import com.keuin.kbackupfabric.util.PrintUtil;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import java.io.IOException;
+import java.util.Objects;
public class RestoreOperation extends InvokableBlockingOperation {
//private static final Logger LOGGER = LogManager.getLogger();
- private final String backupFileName;
private final Thread serverThread;
private final CommandContext<ServerCommandSource> context;
private final MinecraftServer server;
private final ConfiguredBackupMethod configuredBackupMethod;
- public RestoreOperation(CommandContext<ServerCommandSource> context, String backupSavePath, String levelPath, String backupFileName) {
- server = context.getSource().getMinecraftServer();
- this.backupFileName = backupFileName;
- this.serverThread = server.getThread();
- this.context = context;
- this.configuredBackupMethod = new ConfiguredPrimitiveBackupMethod(backupFileName, levelPath, backupSavePath);
+ public RestoreOperation(CommandContext<ServerCommandSource> context, ConfiguredBackupMethod configuredBackupMethod) {
+ server = Objects.requireNonNull(context.getSource().getMinecraftServer());
+ this.serverThread = Objects.requireNonNull(server.getThread());
+ this.context = Objects.requireNonNull(context);
+ this.configuredBackupMethod = Objects.requireNonNull(configuredBackupMethod);
}
@Override
protected boolean blockingContext() {
// do restore to backupName
- PrintUtil.broadcast(String.format("Restoring to backup %s ...", backupFileName));
+ PrintUtil.broadcast(String.format("Restoring to backup %s ...", configuredBackupMethod.getBackupFileName()));
- PrintUtil.debug("Backup file name: " + backupFileName);
+ PrintUtil.debug("Backup file name: " + configuredBackupMethod.getBackupFileName());
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);
@@ -58,7 +56,7 @@ public class RestoreOperation extends InvokableBlockingOperation {
@Override
public String toString() {
- return String.format("restoration from %s", backupFileName);
+ return String.format("restoration from %s", configuredBackupMethod.getBackupFileName());
}
private class WorkerThread implements Runnable {
@@ -71,7 +69,7 @@ public class RestoreOperation extends InvokableBlockingOperation {
while (serverThread.isAlive()) {
try {
serverThread.join();
- } catch (InterruptedException ignored) {
+ } catch (InterruptedException | RuntimeException ignored) {
}
}
@@ -85,7 +83,15 @@ public class RestoreOperation extends InvokableBlockingOperation {
}while(--cnt > 0);
////////////////////
+ long startTime = System.currentTimeMillis();
if (configuredBackupMethod.restore()) {
+ 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 check the manual at: https://github.com/keuin/KBackup-Fabric/blob/master/README.md");
//ServerRestartUtil.forkAndRestart();
System.exit(111);
} else {
@@ -94,9 +100,11 @@ public class RestoreOperation extends InvokableBlockingOperation {
} catch (SecurityException e) {
PrintUtil.error("An exception occurred while restoring: " + e.getMessage());
+ e.printStackTrace();
} catch (IOException e) {
PrintUtil.error(e.toString());
- PrintUtil.error("Failed to restore.");
+ PrintUtil.error("Failed to restore due to an unhandled I/O exception.");
+ e.printStackTrace();
}
System.exit(0); // all failed restoration will eventually go here
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredBackupMethod.java
index b1b8d90..bb80c80 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredBackupMethod.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredBackupMethod.java
@@ -20,4 +20,18 @@ public interface ConfiguredBackupMethod {
boolean restore() throws IOException;
+ /**
+ * Create backup save directory and do some essential initialization before the backup process.
+ *
+ * @return false if failed, then the backup process won't proceed.
+ */
+ boolean touch();
+
+ /**
+ * Get the used backup file name.
+ *
+ * @return the file name.
+ */
+ String getBackupFileName();
+
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethod.java
index 0201d18..b5d2463 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethod.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethod.java
@@ -1,52 +1,92 @@
package com.keuin.kbackupfabric.operation.backup.method;
import com.keuin.kbackupfabric.operation.backup.feedback.IncrementalBackupFeedback;
+import com.keuin.kbackupfabric.util.FilesystemUtil;
+import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.util.backup.incremental.ObjectCollection;
import com.keuin.kbackupfabric.util.backup.incremental.ObjectCollectionFactory;
import com.keuin.kbackupfabric.util.backup.incremental.ObjectCollectionSerializer;
import com.keuin.kbackupfabric.util.backup.incremental.identifier.Sha256Identifier;
import com.keuin.kbackupfabric.util.backup.incremental.manager.IncrementalBackupStorageManager;
-import com.keuin.kbackupfabric.util.backup.name.IncrementalBackupFileNameEncoder;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
-import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.HashSet;
public class ConfiguredIncrementalBackupMethod implements ConfiguredBackupMethod {
- private final String backupFileName;
+ private final String backupIndexFileName;
private final String levelPath;
- private final String backupSavePath;
+ private final String backupIndexFileSaveDirectory;
+ private final String backupBaseDirectory;
- public ConfiguredIncrementalBackupMethod(String backupFileName, String levelPath, String backupSavePath) {
- this.backupFileName = backupFileName;
+ public ConfiguredIncrementalBackupMethod(String backupIndexFileName, String levelPath, String backupIndexFileSaveDirectory, String backupBaseDirectory) {
+ this.backupIndexFileName = backupIndexFileName;
this.levelPath = levelPath;
- this.backupSavePath = backupSavePath;
+ this.backupIndexFileSaveDirectory = backupIndexFileSaveDirectory;
+ this.backupBaseDirectory = backupBaseDirectory;
}
@Override
public IncrementalBackupFeedback backup() throws IOException {
- String customBackupName = new IncrementalBackupFileNameEncoder().decode(backupFileName).customName;
- String backupIndexFileName = new IncrementalBackupFileNameEncoder().encode(customBackupName, LocalDateTime.now());
File levelPathFile = new File(levelPath);
// construct incremental backup index
+ PrintUtil.info("Hashing files...");
ObjectCollection collection = new ObjectCollectionFactory<>(Sha256Identifier.getFactory())
- .fromDirectory(levelPathFile);
+ .fromDirectory(levelPathFile, new HashSet<>(Arrays.asList("session.lock", "kbackup_metadata")));
// update storage
- IncrementalBackupStorageManager storageManager = new IncrementalBackupStorageManager(Paths.get(backupSavePath));
+ PrintUtil.info("Copying files...");
+ IncrementalBackupStorageManager storageManager = new IncrementalBackupStorageManager(Paths.get(backupBaseDirectory));
int filesAdded = storageManager.addObjectCollection(collection, levelPathFile);
// save index file
- ObjectCollectionSerializer.toFile(collection, new File(backupSavePath, backupIndexFileName));
+ PrintUtil.info("Saving index file...");
+ ObjectCollectionSerializer.toFile(collection, new File(backupIndexFileSaveDirectory, backupIndexFileName));
+ // return result
+ PrintUtil.info("Incremental backup finished.");
return new IncrementalBackupFeedback(filesAdded >= 0, filesAdded);
}
@Override
public boolean restore() throws IOException {
- return false;
+ // load collection
+ PrintUtil.info("Loading file list...");
+ ObjectCollection collection = ObjectCollectionSerializer.fromFile(
+ new File(backupIndexFileSaveDirectory, backupIndexFileName)
+ );
+
+ // delete old level
+ File levelPathFile = new File(levelPath);
+ PrintUtil.info("Deleting old level...");
+ if (!FilesystemUtil.forceDeleteDirectory(levelPathFile)) {
+ PrintUtil.info("Failed to delete old level!");
+ return false;
+ }
+
+ // restore file
+ PrintUtil.info("Copying files...");
+ IncrementalBackupStorageManager storageManager = new IncrementalBackupStorageManager(Paths.get(backupBaseDirectory));
+ int restoreObjectCount = storageManager.restoreObjectCollection(collection, levelPathFile);
+
+ PrintUtil.info(String.format("%d file(s) restored.", restoreObjectCount));
+ return true;
+ }
+
+ @Override
+ public boolean touch() {
+ File baseDirectoryFile = new File(backupBaseDirectory);
+ return baseDirectoryFile.isDirectory() || baseDirectoryFile.mkdir();
+ }
+
+ @Override
+ public String getBackupFileName() {
+ return backupIndexFileName;
}
+
+
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java
index c3013e9..1c3c9f6 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java
@@ -15,8 +15,6 @@ import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
-import static org.apache.commons.io.FileUtils.forceDelete;
-
public class ConfiguredPrimitiveBackupMethod implements ConfiguredBackupMethod {
private final String backupFileName;
@@ -56,37 +54,17 @@ public class ConfiguredPrimitiveBackupMethod implements ConfiguredBackupMethod {
public boolean restore() throws IOException {
// Delete old level
PrintUtil.info("Server stopped. Deleting old level ...");
- File levelDirFile = new File(levelPath);
- 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()));
+ if (!FilesystemUtil.forceDeleteDirectory(new File(levelPath))) {
+ PrintUtil.info("Failed to delete old level!");
return false;
}
+
// TODO: Refactor this to the concrete BackupMethod.
// Decompress archive
PrintUtil.info("Decompressing archived level ...");
ZipUtil.unzip(Paths.get(backupSavePath, backupFileName).toString(), levelPath, 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 check the manual at: https://github.com/keuin/KBackup-Fabric/blob/master/README.md");
+
// try {
// Thread.sleep(1000);
@@ -95,4 +73,16 @@ public class ConfiguredPrimitiveBackupMethod implements ConfiguredBackupMethod {
return true;
}
+
+ @Override
+ public boolean touch() {
+ File backupSaveDirectoryFile = new File(backupSavePath);
+ return backupSaveDirectoryFile.isDirectory() || backupSaveDirectoryFile.mkdir();
+ }
+
+ @Override
+ public String getBackupFileName() {
+ return backupFileName;
+ }
+
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java b/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java
index 7f74725..f245cff 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java
@@ -1,6 +1,9 @@
package com.keuin.kbackupfabric.util;
import java.io.File;
+import java.io.IOException;
+
+import static org.apache.commons.io.FileUtils.forceDelete;
public class FilesystemUtil {
@@ -22,12 +25,43 @@ public class FilesystemUtil {
public static long getFileSizeBytes(String filePath) {
long fileSize = -1;
- try{
+ try {
File backupZipFile = new File(filePath);
fileSize = backupZipFile.length();
- } catch (SecurityException ignored){
+ } catch (SecurityException ignored) {
}
return fileSize;
}
+ public static boolean forceDeleteDirectory(File levelDirFile) throws IOException {
+ int failedCounter = 0;
+ final int MAX_RETRY_TIMES = 20;
+ IOException exception = null;
+ while (failedCounter < MAX_RETRY_TIMES) {
+ System.gc();
+ if (!levelDirFile.delete() && levelDirFile.exists()) {
+ System.gc();
+ try {
+ forceDelete(levelDirFile); // Try to force delete.
+ } catch (IOException e) {
+ exception = e;
+ }
+ }
+ if (!levelDirFile.exists())
+ break;
+ ++failedCounter;
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ if (exception != null)
+ throw exception;
+ if (levelDirFile.exists()) {
+ PrintUtil.error(String.format("Cannot restore: failed to delete old level %s .", levelDirFile.getName()));
+ return false;
+ }
+ return true;
+ }
+
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java
index c1aa5fe..837b638 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java
@@ -14,7 +14,8 @@ import java.util.regex.Pattern;
*/
public final class BackupFilesystemUtil {
- private static final String backupSaveDirectoryName = "backups";
+ private static final String BACKUP_SAVE_DIRECTORY_NAME = "backups";
+ private static final String INCREMENTAL_BASE_DIRECTORY_NAME = "incremental";
private static final String backupFileNamePrefix = "kbackup-";
@Deprecated
@@ -43,7 +44,11 @@ public final class BackupFilesystemUtil {
}
public static File getBackupSaveDirectory(MinecraftServer server) {
- return new File(server.getRunDirectory(), backupSaveDirectoryName);
+ return new File(server.getRunDirectory(), BACKUP_SAVE_DIRECTORY_NAME);
+ }
+
+ public static File getIncrementalBackupBaseDirectory(MinecraftServer server) {
+ return new File(server.getRunDirectory(), INCREMENTAL_BASE_DIRECTORY_NAME);
}
public static String getLevelPath(MinecraftServer server) {
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java
index 627cb5c..2f3761c 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactory.java
@@ -1,5 +1,6 @@
package com.keuin.kbackupfabric.util.backup.incremental;
+import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.util.backup.incremental.identifier.FileIdentifierProvider;
import com.keuin.kbackupfabric.util.backup.incremental.identifier.ObjectIdentifier;
@@ -23,26 +24,32 @@ public class ObjectCollectionFactory<T extends ObjectIdentifier> {
this.identifierFactory = identifierFactory;
}
- public ObjectCollection fromDirectory(File directory) throws IOException {
+ public ObjectCollection fromDirectory(File directory, Set<String> ignoredFiles) throws IOException {
final Set<ObjectElement> subFiles = new HashSet<>();
final Map<String, ObjectCollection> subCollections = new HashMap<>();
if (!Objects.requireNonNull(directory).isDirectory())
throw new IllegalArgumentException("given file is not a directory");
- for (Iterator<Path> iter = Files.walk(directory.toPath(), 1).iterator(); iter.hasNext();) {
+ for (Iterator<Path> iter = Files.walk(directory.toPath(), 1).iterator(); iter.hasNext(); ) {
Path path = iter.next();
if (Files.isSameFile(path, directory.toPath()))
continue;
File file = path.toFile();
if (file.isDirectory()) {
- subCollections.put(file.getName(), fromDirectory(file));
- } else {
+ subCollections.put(file.getName(), fromDirectory(file, ignoredFiles));
+ } else if (!ignoredFiles.contains(file.getName())) {
subFiles.add(new ObjectElement(file.getName(), identifierFactory.fromFile(file)));
+ } else {
+ PrintUtil.info(String.format("Skipping file %s.", file.getName()));
}
}
return new ObjectCollection(directory.getName(), subFiles, subCollections);
}
+ public ObjectCollection fromDirectory(File directory) throws IOException {
+ return fromDirectory(directory, Collections.emptySet());
+ }
+
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java
index cd15499..a4271c6 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/manager/IncrementalBackupStorageManager.java
@@ -1,9 +1,11 @@
package com.keuin.kbackupfabric.util.backup.incremental.manager;
+import com.keuin.kbackupfabric.util.PrintUtil;
import com.keuin.kbackupfabric.util.backup.incremental.ObjectCollection;
import com.keuin.kbackupfabric.util.backup.incremental.ObjectElement;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -11,6 +13,8 @@ import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
+import static org.apache.commons.io.FileUtils.forceDelete;
+
public class IncrementalBackupStorageManager {
private final Path backupStorageBase;
@@ -63,13 +67,50 @@ public class IncrementalBackupStorageManager {
int copyCount = 0;
+ // touch directory
+ if (!collectionBasePath.exists()) {
+ int retryCounter = 0;
+ boolean success = false;
+ while (retryCounter++ < 5) {
+ if (collectionBasePath.mkdirs()) {
+ success = true;
+ break;
+ }
+ }
+ if (!success) {
+ throw new IOException("Failed to create directory " + collectionBasePath.getAbsolutePath());
+ }
+ }
+
// copy sub files
for (Map.Entry<String, ObjectElement> entry : collection.getElementMap().entrySet()) {
File copySource = new File(backupStorageBase.toFile(), entry.getValue().getIdentifier().getIdentification());
+ File copyTarget = new File(collectionBasePath.getAbsolutePath(), entry.getKey());
+
if (!baseContainsObject(entry.getValue())) {
throw new IOException(String.format("File %s does not exist in the base.", copySource.getName()));
}
- Files.copy(copySource.toPath(), Paths.get(collectionBasePath.getAbsolutePath(), entry.getKey()));
+ if (copyTarget.exists()) {
+ boolean successDeleting = false;
+ for (int i = 0; i < 5; ++i) {
+ try {
+ forceDelete(copyTarget);
+ successDeleting = true;
+ break;
+ } catch (FileNotFoundException ignored) {
+ break;
+ } catch (IOException e) {
+ PrintUtil.error(String.format("Failed to delete file %s, retry.", copyTarget.getName()));
+ }
+ }
+ if (!successDeleting) {
+ String msg = String.format("Failed to delete file %s.", copyTarget.getName());
+ PrintUtil.error(msg);
+ throw new IOException(msg);
+ }
+ }
+
+ Files.copy(copySource.toPath(), copyTarget.toPath());
++copyCount;
}
diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoder.java
index a0d4128..3c35201 100644
--- a/src/main/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoder.java
+++ b/src/main/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoder.java
@@ -6,7 +6,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IncrementalBackupFileNameEncoder implements BackupFileNameEncoder {
- private static final String backupFileNamePrefix = "incremental-";
+ private static final String backupFileNamePrefix = "incremental";
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
// TODO: make this private and use singleton pattern