summaryrefslogtreecommitdiff
path: root/src/main/java/com/keuin/kbackupfabric/operation
diff options
context:
space:
mode:
authorKeuin <[email protected]>2021-01-21 01:50:08 +0800
committerKeuin <[email protected]>2021-01-21 01:50:08 +0800
commitac3b5e1476dedcefb723f19bd0fdd9a22fcb16e9 (patch)
tree75d0994f3f7aa37c3d240933f0f2d179d318e597 /src/main/java/com/keuin/kbackupfabric/operation
parent82e3986045ac7eaca6aaa290fb2283fd6c6c901a (diff)
parent7a5297de3467b1069fdf5e4a1b2aaf510ca35663 (diff)
Merge remote-tracking branch 'origin/master'
# Conflicts: # src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java
Diffstat (limited to 'src/main/java/com/keuin/kbackupfabric/operation')
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java44
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java20
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java58
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java44
-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.java99
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/BackupFeedback.java6
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/IncrementalBackupFeedback.java28
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java30
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredBackupMethod.java37
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethod.java92
-rw-r--r--src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java88
13 files changed, 337 insertions, 361 deletions
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
index d03c347..b38921d 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java
@@ -1,39 +1,32 @@
package com.keuin.kbackupfabric.operation;
-import com.keuin.kbackupfabric.exception.ZipUtilException;
-import com.keuin.kbackupfabric.metadata.BackupMetadata;
import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation;
-import com.keuin.kbackupfabric.operation.backup.BackupMethod;
+import com.keuin.kbackupfabric.operation.backup.feedback.BackupFeedback;
+import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod;
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 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.backup.BackupFilesystemUtil.*;
import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo;
public class BackupOperation extends InvokableAsyncBlockingOperation {
private final CommandContext<ServerCommandSource> context;
- private final String backupName;
private final Map<World, Boolean> oldWorldsSavingDisabled = new HashMap<>();
- private final BackupMethod backupMethod;
+ private final ConfiguredBackupMethod configuredBackupMethod;
private long startTime;
- public BackupOperation(CommandContext<ServerCommandSource> context, String backupName, BackupMethod backupMethod) {
+ public BackupOperation(CommandContext<ServerCommandSource> context, ConfiguredBackupMethod configuredBackupMethod) {
super("BackupWorker");
this.context = context;
- this.backupName = backupName;
- this.backupMethod = backupMethod;
+ this.configuredBackupMethod = configuredBackupMethod;
}
@Override
@@ -44,27 +37,20 @@ 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
- String levelPath = getLevelPath(server);
- String backupFileName = getBackupFileName(backupName);
-
- BackupMethod.BackupResult result = backupMethod.backup(backupName,levelPath,backupSaveDirectory);
- if(result.isSuccess()) {
- // Restore old autosave switch stat
+ // Backup
+ BackupFeedback result = configuredBackupMethod.backup();
+ if (result.isSuccess()) {
+ // Restore old auto-save switch stat
server.getWorlds().forEach(world -> world.savingDisabled = oldWorldsSavingDisabled.getOrDefault(world, true));
// Print finish message: time elapsed and file size
long timeElapsedMillis = System.currentTimeMillis() - startTime;
- String msgText = String.format("Backup finished. Time elapsed: %.2fs.", timeElapsedMillis / 1000.0);
- File backupZipFile = new File(backupSaveDirectory, backupFileName);
- msgText += String.format(" File size: %s.", humanFileSize(result.getBackupSizeBytes()));
+ String msgText = String.format("Backup finished. Time elapsed: %.2fs. ", timeElapsedMillis / 1000.0) + result.getFeedback();
PrintUtil.msgInfo(context, msgText, true);
} else {
// failed
@@ -79,14 +65,14 @@ public class BackupOperation extends InvokableAsyncBlockingOperation {
@Override
protected boolean sync() {
- //// Save world, save old autosave configs
+ //// Save world, save old auto-save configs
- PrintUtil.broadcast(String.format("Making backup %s, please wait ...", backupName));
+ PrintUtil.broadcast("Making backup, please wait ...");
// Get server
MinecraftServer server = context.getSource().getMinecraftServer();
- // Save old autosave switch stat temporally
+ // Save old auto-save switch state for restoration after finished
oldWorldsSavingDisabled.clear();
server.getWorlds().forEach(world -> {
oldWorldsSavingDisabled.put(world, world.savingDisabled);
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
index 444ca9a..3ae09f5 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/DeleteOperation.java
@@ -1,8 +1,8 @@
package com.keuin.kbackupfabric.operation;
import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation;
-import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider;
import com.keuin.kbackupfabric.util.PrintUtil;
+import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
@@ -10,27 +10,26 @@ import net.minecraft.server.command.ServerCommandSource;
import java.io.File;
import java.io.IOException;
-import static com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil.getBackupFileName;
-import static com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil.getBackupSaveDirectory;
import static com.keuin.kbackupfabric.util.PrintUtil.msgErr;
import static com.keuin.kbackupfabric.util.PrintUtil.msgInfo;
+import static com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil.getBackupSaveDirectory;
import static org.apache.commons.io.FileUtils.forceDelete;
public class DeleteOperation extends InvokableAsyncBlockingOperation {
//private static final Logger LOGGER = LogManager.getLogger();
- private final String backupName;
+ private final String backupFileName;
private final CommandContext<ServerCommandSource> context;
- public DeleteOperation(CommandContext<ServerCommandSource> context, String backupName) {
+ public DeleteOperation(CommandContext<ServerCommandSource> context, String backupFileName) {
super("BackupDeletingWorker");
- this.backupName = backupName;
+ this.backupFileName = backupFileName;
this.context = context;
}
@Override
public String toString() {
- return String.format("deletion of %s", backupName);
+ return String.format("deletion of %s", backupFileName);
}
@Override
@@ -41,8 +40,7 @@ public class DeleteOperation extends InvokableAsyncBlockingOperation {
private void delete() {
MinecraftServer server = context.getSource().getMinecraftServer();
- String backupFileName = getBackupFileName(backupName);
- PrintUtil.info("Deleting backup " + backupName);
+ PrintUtil.info("Deleting backup file " + this.backupFileName);
File backupFile = new File(getBackupSaveDirectory(server), backupFileName);
int tryCounter = 0;
do {
@@ -59,7 +57,7 @@ public class DeleteOperation extends InvokableAsyncBlockingOperation {
}
++tryCounter;
} while (backupFile.exists());
- PrintUtil.info("Deleted backup " + backupName);
- msgInfo(context, "Deleted backup " + backupName);
+ PrintUtil.info("Successfully deleted backup file " + this.backupFileName);
+ msgInfo(context, "Successfully deleted backup file " + this.backupFileName);
}
}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
index 22397a1..b870746 100644
--- a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
+++ b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java
@@ -1,47 +1,36 @@
package com.keuin.kbackupfabric.operation;
-import com.keuin.kbackupfabric.exception.ZipUtilException;
import com.keuin.kbackupfabric.operation.abstracts.InvokableBlockingOperation;
+import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod;
import com.keuin.kbackupfabric.util.PrintUtil;
-import com.keuin.kbackupfabric.util.ZipUtil;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
-import java.io.File;
import java.io.IOException;
-
-import static com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil.getBackupFileName;
-import static com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil.getBackupSaveDirectory;
-import static org.apache.commons.io.FileUtils.forceDelete;
+import java.util.Objects;
public class RestoreOperation extends InvokableBlockingOperation {
//private static final Logger LOGGER = LogManager.getLogger();
- private final String backupName;
private final Thread serverThread;
- private final String backupFilePath;
- private final String levelDirectory;
private final CommandContext<ServerCommandSource> context;
private final MinecraftServer server;
+ private final ConfiguredBackupMethod configuredBackupMethod;
- public RestoreOperation(CommandContext<ServerCommandSource> context, String backupFilePath, String levelDirectory, String backupName) {
- server = context.getSource().getMinecraftServer();
- this.backupName = backupName;
- this.serverThread = server.getThread();
- this.backupFilePath = backupFilePath;
- this.levelDirectory = levelDirectory;
- this.context = context;
+ 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 previous world %s ...", backupName));
+ PrintUtil.broadcast(String.format("Restoring to backup %s ...", configuredBackupMethod.getBackupFileName()));
- String backupFileName = getBackupFileName(backupName);
- PrintUtil.debug("Backup file name: " + backupFileName);
- File backupFile = new File(getBackupSaveDirectory(server), 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);
@@ -67,7 +56,7 @@ public class RestoreOperation extends InvokableBlockingOperation {
@Override
public String toString() {
- return String.format("restoration from %s", backupName);
+ return String.format("restoration from %s", configuredBackupMethod.getBackupFileName());
}
private class WorkerThread implements Runnable {
@@ -80,7 +69,7 @@ public class RestoreOperation extends InvokableBlockingOperation {
while (serverThread.isAlive()) {
try {
serverThread.join();
- } catch (InterruptedException ignored) {
+ } catch (InterruptedException | RuntimeException ignored) {
}
}
@@ -94,13 +83,30 @@ public class RestoreOperation extends InvokableBlockingOperation {
}while(--cnt > 0);
////////////////////
-
- //ServerRestartUtil.forkAndRestart();
- System.exit(111);
+ 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 {
+ PrintUtil.error("Failed to restore! server will not restart automatically.");
+ }
} 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 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/BackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java
deleted file mode 100644
index 4e9eb6c..0000000
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package com.keuin.kbackupfabric.operation.backup;
-
-import com.keuin.kbackupfabric.util.backup.builder.BackupFileNameBuilder;
-import com.keuin.kbackupfabric.util.backup.formatter.BackupFileNameFormatter;
-
-import java.io.IOException;
-
-/**
- * Provide specific backup method, which is implemented statelessly.
- */
-public interface BackupMethod {
-
- /**
- * Perform a backup with given method. The backup will be saved as the given name.
- * Note: real file name depends on the backup type.
- * @param backupName the backup name.
- * @return if the backup operation succeed.
- */
- 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
deleted file mode 100644
index 4a87bb3..0000000
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupMethod.java
+++ /dev/null
@@ -1,62 +0,0 @@
-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
deleted file mode 100644
index f90aef1..0000000
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/IncrementalBackupUtil.java
+++ /dev/null
@@ -1,90 +0,0 @@
-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
deleted file mode 100644
index 72e9cdb..0000000
--- a/src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java
+++ /dev/null
@@ -1,99 +0,0 @@
-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;
-import com.keuin.kbackupfabric.util.backup.formatter.BackupFileNameFormatter;
-
-import java.io.File;
-import java.io.IOException;
-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();
- private static int zipLevel = 9;
-
- public static PrimitiveBackupMethod getInstance() {
- return INSTANCE;
- }
-
- @Override
- public BackupResult backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException {
- String backupFileName = BackupFileNameBuilder.primitiveZipBackup().build(LocalDateTime.now(),backupName);
- try {
- BackupMetadata backupMetadata = new BackupMetadata(System.currentTimeMillis(), backupName);
-
- PrintUtil.info(String.format("zip(srcPath=%s, destPath=%s)", levelPath, backupSaveDirectory));
- PrintUtil.info("Compressing level ...");
- ZipUtil.makeBackupZip(levelPath, backupSaveDirectory, backupFileName, backupMetadata, zipLevel);
-
- } 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;
- }
-
- @Override
- public BackupFileNameBuilder getBackupFileNameBuilder() {
- return BackupFileNameBuilder.primitiveZipBackup();
- }
-
- @Override
- public BackupFileNameFormatter getBackupFileNameFormatter() {
- return BackupFileNameFormatter.primitiveZipBackup();
- }
-}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/BackupFeedback.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/BackupFeedback.java
new file mode 100644
index 0000000..0fe0766
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/BackupFeedback.java
@@ -0,0 +1,6 @@
+package com.keuin.kbackupfabric.operation.backup.feedback;
+
+public interface BackupFeedback {
+ String getFeedback();
+ boolean isSuccess();
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/IncrementalBackupFeedback.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/IncrementalBackupFeedback.java
new file mode 100644
index 0000000..f39fde6
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/IncrementalBackupFeedback.java
@@ -0,0 +1,28 @@
+package com.keuin.kbackupfabric.operation.backup.feedback;
+
+public class IncrementalBackupFeedback implements BackupFeedback {
+ private final boolean success;
+ private final int newFilesAdded;
+
+ public IncrementalBackupFeedback(boolean success, int newFilesAdded) {
+ this.success = success;
+ this.newFilesAdded = newFilesAdded;
+ }
+
+ @Override
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public long getNewFilesAdded() {
+ return newFilesAdded;
+ }
+
+ @Override
+ public String getFeedback() {
+ if (success && newFilesAdded >= 0)
+ return String.format("File(s) added: %d.", newFilesAdded);
+ else
+ return "";
+ }
+}
diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java
new file mode 100644
index 0000000..3bcd012
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java
@@ -0,0 +1,30 @@
+package com.keuin.kbackupfabric.operation.backup.feedback;
+
+import static com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil.getFriendlyFileSizeString;
+
+public class PrimitiveBackupFeedback implements BackupFeedback {
+ private final boolean success;
+ private final long backupSizeBytes;
+
+ public PrimitiveBackupFeedback(boolean success, long backupSizeBytes) {
+ this.success = success;
+ this.backupSizeBytes = backupSizeBytes;
+ }
+
+ @Override
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public long getBackupSizeBytes() {
+ return backupSizeBytes;
+ }
+
+ @Override
+ public String getFeedback() {
+ if (success && backupSizeBytes >= 0)
+ return String.format("File size: %s.", getFriendlyFileSizeString(backupSizeBytes));
+ else
+ return "";
+ }
+}
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
new file mode 100644
index 0000000..bb80c80
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredBackupMethod.java
@@ -0,0 +1,37 @@
+package com.keuin.kbackupfabric.operation.backup.method;
+
+import com.keuin.kbackupfabric.operation.backup.feedback.BackupFeedback;
+
+import java.io.IOException;
+
+/**
+ * Provide specific backup method, which has been configured with proper settings,
+ * such as saving directory and level path.
+ */
+public interface ConfiguredBackupMethod {
+
+ /**
+ * Perform a backup with given method. The backup will be saved as the given name.
+ * Note: real file name depends on the backup type.
+ *
+ * @return backup result.
+ */
+ BackupFeedback backup() throws IOException;
+
+ 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
new file mode 100644
index 0000000..b5d2463
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethod.java
@@ -0,0 +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 java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.HashSet;
+
+public class ConfiguredIncrementalBackupMethod implements ConfiguredBackupMethod {
+
+ private final String backupIndexFileName;
+ private final String levelPath;
+ private final String backupIndexFileSaveDirectory;
+ private final String backupBaseDirectory;
+
+ public ConfiguredIncrementalBackupMethod(String backupIndexFileName, String levelPath, String backupIndexFileSaveDirectory, String backupBaseDirectory) {
+ this.backupIndexFileName = backupIndexFileName;
+ this.levelPath = levelPath;
+ this.backupIndexFileSaveDirectory = backupIndexFileSaveDirectory;
+ this.backupBaseDirectory = backupBaseDirectory;
+ }
+
+ @Override
+ public IncrementalBackupFeedback backup() throws IOException {
+ File levelPathFile = new File(levelPath);
+
+ // construct incremental backup index
+ PrintUtil.info("Hashing files...");
+ ObjectCollection collection = new ObjectCollectionFactory<>(Sha256Identifier.getFactory())
+ .fromDirectory(levelPathFile, new HashSet<>(Arrays.asList("session.lock", "kbackup_metadata")));
+
+ // update storage
+ PrintUtil.info("Copying files...");
+ IncrementalBackupStorageManager storageManager = new IncrementalBackupStorageManager(Paths.get(backupBaseDirectory));
+ int filesAdded = storageManager.addObjectCollection(collection, levelPathFile);
+
+ // save index file
+ 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 {
+ // 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
new file mode 100644
index 0000000..1c3c9f6
--- /dev/null
+++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredPrimitiveBackupMethod.java
@@ -0,0 +1,88 @@
+package com.keuin.kbackupfabric.operation.backup.method;
+
+import com.keuin.kbackupfabric.exception.ZipUtilException;
+import com.keuin.kbackupfabric.metadata.BackupMetadata;
+import com.keuin.kbackupfabric.operation.backup.feedback.PrimitiveBackupFeedback;
+import com.keuin.kbackupfabric.util.FilesystemUtil;
+import com.keuin.kbackupfabric.util.PrintUtil;
+import com.keuin.kbackupfabric.util.ZipUtil;
+import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil;
+import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter;
+import com.keuin.kbackupfabric.util.backup.name.PrimitiveBackupFileNameEncoder;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+
+public class ConfiguredPrimitiveBackupMethod implements ConfiguredBackupMethod {
+
+ private final String backupFileName;
+ private final String levelPath;
+ private final String backupSavePath;
+
+ public ConfiguredPrimitiveBackupMethod(String backupFileName, String levelPath, String backupSavePath) {
+ this.backupFileName = backupFileName;
+ this.levelPath = levelPath;
+ this.backupSavePath = backupSavePath;
+ }
+
+ @Deprecated
+ private String getBackupFileName(LocalDateTime time, String backupName) {
+ String timeString = BackupNameTimeFormatter.localDateTimeToString(time);
+ return String.format("%s%s_%s%s", BackupFilesystemUtil.getBackupFileNamePrefix(), timeString, backupName, ".zip");
+ }
+
+ @Override
+ public PrimitiveBackupFeedback backup() throws IOException {
+ try {
+ String customBackupName = new PrimitiveBackupFileNameEncoder().decode(backupFileName).customName;
+ BackupMetadata backupMetadata = new BackupMetadata(System.currentTimeMillis(), customBackupName);
+ PrintUtil.info(String.format("zip(srcPath=%s, destPath=%s)", levelPath, backupSavePath));
+ PrintUtil.info("Compressing level ...");
+ ZipUtil.makeBackupZip(levelPath, backupSavePath, backupFileName, backupMetadata);
+ } catch (ZipUtilException exception) {
+ PrintUtil.info("Infinite recursive of directory tree detected, backup was aborted.");
+ return new PrimitiveBackupFeedback(false, 0);
+ }
+
+ // Get backup file size and return
+ return new PrimitiveBackupFeedback(true, FilesystemUtil.getFileSizeBytes(backupSavePath, backupFileName));
+ }
+
+ @Override
+ public boolean restore() throws IOException {
+ // Delete old level
+ PrintUtil.info("Server stopped. Deleting old level ...");
+ 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);
+
+
+// try {
+// Thread.sleep(1000);
+// } catch (InterruptedException ignored) {
+// }
+
+ return true;
+ }
+
+ @Override
+ public boolean touch() {
+ File backupSaveDirectoryFile = new File(backupSavePath);
+ return backupSaveDirectoryFile.isDirectory() || backupSaveDirectoryFile.mkdir();
+ }
+
+ @Override
+ public String getBackupFileName() {
+ return backupFileName;
+ }
+
+}