From 81497ebee2cdb0dac3bcdc26c5d77d06bdb98b5d Mon Sep 17 00:00:00 2001 From: Keuin Date: Wed, 13 Jan 2021 12:51:15 +0800 Subject: Clean old incremental backup codes. --- .../util/backup/name/BackupFileNameEncoder.java | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java (limited to 'src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java') diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java new file mode 100644 index 0000000..83967b7 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java @@ -0,0 +1,49 @@ +package com.keuin.kbackupfabric.util.backup.name; + +import java.time.LocalDateTime; + +/** + * Encode and decode backup file name for a specific backup type. + */ +public interface BackupFileNameEncoder { + + /** + * Construct full backup file name from custom name and creation time. + * @param customName the custom name. If the custom name contains invalid chars, an exception will be thrown. + * @param time the creation time. + * @return the file name. + */ + String encode(String customName, LocalDateTime time); + + /** + * Extract custom and backup time from backup file name. + * @param fileName the backup file name. + * @return the information. If the given file name is invalid, return null. + */ + BackupBasicInformation decode(String fileName); + + /** + * Check if the given string is a valid custom backup name. + * @param customName the custom backup name. + * @return if the name is valid. + */ + default boolean isValidCustomName(String customName) { + final char[] ILLEGAL_CHARACTERS = {'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':'}; + for (char c : ILLEGAL_CHARACTERS) { + if (customName.contains(String.valueOf(c))) { + return false; + } + } + return true; + } + + class BackupBasicInformation { + public final String customName; + public final LocalDateTime time; + + BackupBasicInformation(String customName, LocalDateTime time) { + this.customName = customName; + this.time = time; + } + } +} -- cgit v1.2.3 From c82e1ee286e89f6e36e8920282d348ce8cfae9cf Mon Sep 17 00:00:00 2001 From: Keuin Date: Wed, 13 Jan 2021 13:22:19 +0800 Subject: Refactor for a better code quality --- .../java/com/keuin/kbackupfabric/KBCommands.java | 67 +++++++++------ .../kbackupfabric/operation/BackupOperation.java | 6 +- .../kbackupfabric/operation/RestoreOperation.java | 15 ++-- .../operation/backup/BackupMethod.java | 22 ----- .../operation/backup/PrimitiveBackupMethod.java | 95 ---------------------- .../operation/backup/feedback/BackupFeedback.java | 1 + .../backup/feedback/IncrementalBackupFeedback.java | 28 +++++++ .../backup/feedback/PrimitiveBackupFeedback.java | 3 +- .../operation/backup/method/BackupMethod.java | 22 +++++ .../backup/method/IncrementalBackupMethod.java | 17 ++++ .../backup/method/PrimitiveBackupMethod.java | 95 ++++++++++++++++++++++ .../util/backup/name/BackupFileNameEncoder.java | 9 ++ .../name/IncrementalBackupFileNameEncoder.java | 33 ++++++++ .../name/PrimitiveBackupFileNameEncoder.java | 1 - 14 files changed, 262 insertions(+), 152 deletions(-) delete mode 100644 src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java delete mode 100644 src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/IncrementalBackupFeedback.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/backup/method/BackupMethod.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/backup/method/IncrementalBackupMethod.java create mode 100644 src/main/java/com/keuin/kbackupfabric/operation/backup/method/PrimitiveBackupMethod.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoder.java (limited to 'src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java') diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java index ef4a401..0e268ce 100644 --- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java +++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java @@ -5,9 +5,10 @@ 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.PrimitiveBackupMethod; +import com.keuin.kbackupfabric.operation.backup.method.PrimitiveBackupMethod; import com.keuin.kbackupfabric.util.PrintUtil; import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil; +import com.keuin.kbackupfabric.util.backup.name.PrimitiveBackupFileNameEncoder; import com.keuin.kbackupfabric.util.backup.suggestion.BackupNameSuggestionProvider; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.context.CommandContext; @@ -82,23 +83,24 @@ public final class KBCommands { (dir, name) -> dir.isDirectory() && name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix()) ); - backupFileNameList.clear(); - if (files != null) { - if (files.length != 0) { - msgInfo(context, "Available backups: (file is not checked, manipulation may affect this plugin)"); + synchronized (backupFileNameList) { + backupFileNameList.clear(); + if (files != null) { + if (files.length != 0) { + msgInfo(context, "Available backups: (file is not checked, manipulation may affect this plugin)"); + } else { + msgInfo(context, "There are no available backups. To make a new backup, run /kb backup."); + } + int i = 0; + for (File file : files) { + ++i; + String backupFileName = file.getName(); + msgInfo(context, String.format("[%d] %s", i, getPrimitiveBackupInformationString(backupFileName, file.length()))); + backupFileNameList.add(backupFileName); + } } else { - msgInfo(context, "There are no available backups. To make a new backup, run /kb backup."); + msgErr(context, "Error: failed to list files in backup folder."); } - int i = 0; - for (File file : files) { - ++i; - String backupFileName = file.getName(); - String sizeString = getFriendlyFileSizeString(file.length()); - msgInfo(context, String.format("[%d] %s, size: %s", i, backupFileName, sizeString)); - backupFileNameList.add(backupFileName); - } - } else { - msgErr(context, "Error: failed to list files in backup folder."); } return SUCCESS; } @@ -293,14 +295,21 @@ public final class KBCommands { files.sort((x, y) -> (int) (BackupFilesystemUtil.getBackupTimeFromBackupFileName(y.getName()) - BackupFilesystemUtil.getBackupTimeFromBackupFileName(x.getName()))); File prevBackupFile = files.get(0); String backupFileName = prevBackupFile.getName(); - int i = backupFileNameList.indexOf(backupFileName); - if (i == -1) { - backupFileNameList.add(backupFileName); - i = backupFileNameList.size(); - } else { - ++i; + int i; + synchronized (backupFileNameList) { + i = backupFileNameList.indexOf(backupFileName); + if (i == -1) { + backupFileNameList.add(backupFileName); + i = backupFileNameList.size(); + } else { + ++i; + } } - msgInfo(context, String.format("The most recent backup: [%d] %s , size: %s", i, backupFileName, getFriendlyFileSizeString(prevBackupFile.length()))); + msgInfo(context, String.format( + "The most recent backup: [%d] %s", + i, + getPrimitiveBackupInformationString(backupFileName, prevBackupFile.length()) + )); } catch (NullPointerException e) { msgInfo(context, "There are no backups available."); } catch (SecurityException ignored) { @@ -310,6 +319,14 @@ public final class KBCommands { return SUCCESS; } + private static String getPrimitiveBackupInformationString(String backupFileName, long backupFileSizeBytes) { + return String.format( + "%s , size: %s", + new PrimitiveBackupFileNameEncoder().decode(backupFileName), + getFriendlyFileSizeString(backupFileSizeBytes) + ); + } + // /** // * Select the backup method we use. // * @param context the context. @@ -338,7 +355,9 @@ public final class KBCommands { if (backupName.matches("[0-9]*")) { // If numeric input int index = Integer.parseInt(backupName) - 1; - return backupFileNameList.get(index); // Replace input number with real backup file name. + synchronized (backupFileNameList) { + return backupFileNameList.get(index); // Replace input number with real backup file name. + } } } catch (NumberFormatException | IndexOutOfBoundsException ignored) { } diff --git a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java index f8736c8..3183cf0 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/BackupOperation.java @@ -1,8 +1,8 @@ package com.keuin.kbackupfabric.operation; import com.keuin.kbackupfabric.operation.abstracts.InvokableAsyncBlockingOperation; -import com.keuin.kbackupfabric.operation.backup.BackupMethod; -import com.keuin.kbackupfabric.operation.backup.feedback.PrimitiveBackupFeedback; +import com.keuin.kbackupfabric.operation.backup.feedback.BackupFeedback; +import com.keuin.kbackupfabric.operation.backup.method.BackupMethod; import com.keuin.kbackupfabric.util.PrintUtil; import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.MinecraftServer; @@ -52,7 +52,7 @@ public class BackupOperation extends InvokableAsyncBlockingOperation { String levelPath = getLevelPath(server); String backupFileName = getBackupFileName(customBackupName); - PrimitiveBackupFeedback result = backupMethod.backup(customBackupName,levelPath,backupSaveDirectory); + BackupFeedback result = backupMethod.backup(customBackupName,levelPath,backupSaveDirectory); if(result.isSuccess()) { // Restore old auto-save switch stat server.getWorlds().forEach(world -> world.savingDisabled = oldWorldsSavingDisabled.getOrDefault(world, true)); diff --git a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java index b95c767..02b76f1 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/RestoreOperation.java @@ -1,8 +1,8 @@ package com.keuin.kbackupfabric.operation; import com.keuin.kbackupfabric.operation.abstracts.InvokableBlockingOperation; -import com.keuin.kbackupfabric.operation.backup.BackupMethod; -import com.keuin.kbackupfabric.operation.backup.PrimitiveBackupMethod; +import com.keuin.kbackupfabric.operation.backup.method.BackupMethod; +import com.keuin.kbackupfabric.operation.backup.method.PrimitiveBackupMethod; import com.keuin.kbackupfabric.util.PrintUtil; import com.mojang.brigadier.context.CommandContext; import net.minecraft.server.MinecraftServer; @@ -94,10 +94,12 @@ public class RestoreOperation extends InvokableBlockingOperation { }while(--cnt > 0); //////////////////// - backupMethod.restore(backupFileName, levelPath, backupSavePath); - - //ServerRestartUtil.forkAndRestart(); - System.exit(111); + if (backupMethod.restore(backupFileName, levelPath, backupSavePath)) { + //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()); @@ -105,6 +107,7 @@ public class RestoreOperation extends InvokableBlockingOperation { PrintUtil.error(e.toString()); PrintUtil.error("Failed to restore."); } + 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 b65a076..0000000 --- a/src/main/java/com/keuin/kbackupfabric/operation/backup/BackupMethod.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.keuin.kbackupfabric.operation.backup; - -import com.keuin.kbackupfabric.operation.backup.feedback.PrimitiveBackupFeedback; - -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. - */ - PrimitiveBackupFeedback backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException; - - boolean restore(String backupName, String levelPath, String backupSaveDirectory) throws IOException; - -} 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 9c065a6..0000000 --- a/src/main/java/com/keuin/kbackupfabric/operation/backup/PrimitiveBackupMethod.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.keuin.kbackupfabric.operation.backup; - -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; - -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; - } - - @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(String customBackupName, String levelPath, String backupSavePath) throws IOException { -// String backupFileName = getBackupFileName(LocalDateTime.now(),backupName); - String backupFileName = new PrimitiveBackupFileNameEncoder().encode(customBackupName, LocalDateTime.now()); - try { - 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(String backupFileName, 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, backupFileName).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 check the manual at: https://github.com/keuin/KBackup-Fabric/blob/master/README.md"); - -// try { -// Thread.sleep(1000); -// } catch (InterruptedException ignored) { -// } - - return true; - } -} 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 index 92a9f39..0fe0766 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/BackupFeedback.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/BackupFeedback.java @@ -2,4 +2,5 @@ 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 index 6d7a15b..3bcd012 100644 --- a/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java +++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/feedback/PrimitiveBackupFeedback.java @@ -11,6 +11,7 @@ public class PrimitiveBackupFeedback implements BackupFeedback { this.backupSizeBytes = backupSizeBytes; } + @Override public boolean isSuccess() { return success; } @@ -22,7 +23,7 @@ public class PrimitiveBackupFeedback implements BackupFeedback { @Override public String getFeedback() { if (success && backupSizeBytes >= 0) - return String.format(" File size: %s.", getFriendlyFileSizeString(backupSizeBytes)); + return String.format("File size: %s.", getFriendlyFileSizeString(backupSizeBytes)); else return ""; } diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/BackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/BackupMethod.java new file mode 100644 index 0000000..25a5952 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/BackupMethod.java @@ -0,0 +1,22 @@ +package com.keuin.kbackupfabric.operation.backup.method; + +import com.keuin.kbackupfabric.operation.backup.feedback.BackupFeedback; + +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. + */ + BackupFeedback backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException; + + boolean restore(String backupName, String levelPath, String backupSaveDirectory) throws IOException; + +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/IncrementalBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/IncrementalBackupMethod.java new file mode 100644 index 0000000..1dacc44 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/IncrementalBackupMethod.java @@ -0,0 +1,17 @@ +package com.keuin.kbackupfabric.operation.backup.method; + +import com.keuin.kbackupfabric.operation.backup.feedback.IncrementalBackupFeedback; + +import java.io.IOException; + +public class IncrementalBackupMethod implements BackupMethod { + @Override + public IncrementalBackupFeedback backup(String backupName, String levelPath, String backupSaveDirectory) throws IOException { + return null; + } + + @Override + public boolean restore(String backupName, String levelPath, String backupSaveDirectory) throws IOException { + return false; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/operation/backup/method/PrimitiveBackupMethod.java b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/PrimitiveBackupMethod.java new file mode 100644 index 0000000..1f15346 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/operation/backup/method/PrimitiveBackupMethod.java @@ -0,0 +1,95 @@ +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; + +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; + } + + @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(String customBackupName, String levelPath, String backupSavePath) throws IOException { +// String backupFileName = getBackupFileName(LocalDateTime.now(),backupName); + String backupFileName = new PrimitiveBackupFileNameEncoder().encode(customBackupName, LocalDateTime.now()); + try { + 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(String backupFileName, 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, backupFileName).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 check the manual at: https://github.com/keuin/KBackup-Fabric/blob/master/README.md"); + +// try { +// Thread.sleep(1000); +// } catch (InterruptedException ignored) { +// } + + return true; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java index 83967b7..972403b 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java @@ -1,6 +1,7 @@ package com.keuin.kbackupfabric.util.backup.name; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; /** * Encode and decode backup file name for a specific backup type. @@ -38,12 +39,20 @@ public interface BackupFileNameEncoder { } class BackupBasicInformation { + public final String customName; public final LocalDateTime time; + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm.ss"); + BackupBasicInformation(String customName, LocalDateTime time) { this.customName = customName; this.time = time; } + + @Override + public String toString() { + return String.format("%s, %s", customName, time.format(formatter)); + } } } 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 new file mode 100644 index 0000000..ae54930 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoder.java @@ -0,0 +1,33 @@ +package com.keuin.kbackupfabric.util.backup.name; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class IncrementalBackupFileNameEncoder implements BackupFileNameEncoder { + private static final String backupFileNamePrefix = "incremental-"; + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); + + @Override + public String encode(String customName, LocalDateTime time) { + if (!isValidCustomName(customName)) + throw new IllegalArgumentException("Invalid custom name"); + String timeString = time.format(formatter); + return backupFileNamePrefix + "-" + timeString + "_" + customName + ".kbi"; + } + + @Override + public BackupFileNameEncoder.BackupBasicInformation decode(String fileName) { + Pattern pattern = Pattern.compile( + backupFileNamePrefix + "-" + "([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_(.+)\\.kbi" + ); + Matcher matcher = pattern.matcher(fileName); + if (matcher.find()) { + String timeString = matcher.group(1); + String customName = matcher.group(2); + return new BackupFileNameEncoder.BackupBasicInformation(customName, LocalDateTime.parse(timeString, formatter)); + } + return null; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java index 39c2403..bcba114 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java @@ -6,7 +6,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public class PrimitiveBackupFileNameEncoder implements BackupFileNameEncoder { - private static final String backupFileNamePrefix = "kbackup"; private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); -- cgit v1.2.3 From 6baece29cccc906651331cbf8a90a06f8cee045b Mon Sep 17 00:00:00 2001 From: Keuin Date: Thu, 14 Jan 2021 13:00:52 +0800 Subject: Fix a minor naming bug. Code refactor. Improve test. --- .../java/com/keuin/kbackupfabric/KBCommands.java | 4 +- .../com/keuin/kbackupfabric/util/BytesUtil.java | 17 ++++++- .../util/backup/BackupFilesystemUtil.java | 4 -- .../identifier/FileIdentifierProvider.java | 7 +++ .../incremental/identifier/Sha256Identifier.java | 20 ++++++++- .../identifier/SingleHashIdentifier.java | 16 ++++++- .../identifier/StorageObjectLoader.java | 25 +++++++++++ .../manager/IncrementalBackupStorageManager.java | 52 +++++++++++++++++++++- .../util/backup/name/BackupFileNameEncoder.java | 8 +++- .../name/IncrementalBackupFileNameEncoder.java | 2 +- .../name/PrimitiveBackupFileNameEncoder.java | 2 +- .../backup/provider/AvailableBackupProvider.java | 8 ++++ .../provider/IncrementalBackupInformation.java | 13 ++++++ .../provider/PrimitiveBackupInformation.java | 14 ++++++ .../ConfiguredIncrementalBackupMethodTest.java | 34 +++++++++++--- .../incremental/ObjectCollectionFactoryTest.java | 14 +++--- .../identifier/Sha256IdentifierTest.java | 2 +- .../name/IncrementalBackupFileNameEncoderTest.java | 38 ++++++++++++++++ .../name/PrimitiveBackupFileNameEncoderTest.java | 33 +++++++++++++- 19 files changed, 281 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/StorageObjectLoader.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/provider/AvailableBackupProvider.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/provider/IncrementalBackupInformation.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/provider/PrimitiveBackupInformation.java create mode 100644 src/test/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoderTest.java (limited to 'src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java') diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java index 847fe7c..98f6369 100644 --- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java +++ b/src/main/java/com/keuin/kbackupfabric/KBCommands.java @@ -84,7 +84,9 @@ public final class KBCommands { public static int list(CommandContext context) { MinecraftServer server = context.getSource().getMinecraftServer(); File[] files = getBackupSaveDirectory(server).listFiles( - (dir, name) -> dir.isDirectory() && name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix()) + (dir, name) -> dir.isDirectory() && + (name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix()) + || name.toLowerCase().endsWith(".kbi")) ); synchronized (backupFileNameList) { diff --git a/src/main/java/com/keuin/kbackupfabric/util/BytesUtil.java b/src/main/java/com/keuin/kbackupfabric/util/BytesUtil.java index 6ded7b8..c33c028 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/BytesUtil.java +++ b/src/main/java/com/keuin/kbackupfabric/util/BytesUtil.java @@ -4,6 +4,7 @@ import java.nio.charset.StandardCharsets; public class BytesUtil { private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + public static String bytesToHex(byte[] bytes) { byte[] hexChars = new byte[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { @@ -11,6 +12,20 @@ public class BytesUtil { hexChars[j * 2] = HEX_ARRAY[v >>> 4]; hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; } - return new String(hexChars, StandardCharsets.UTF_8); + return new String(hexChars, StandardCharsets.UTF_8).toUpperCase(); + } + + public static byte[] hexToBytes(String s) { + int len = s.length(); + if (len % 2 != 0) + throw new IllegalArgumentException("Invalid hex string."); + byte[] b = new byte[len / 2]; + int index, v; + for (int i = 0; i < b.length; i++) { + index = i * 2; + v = Integer.parseInt(s.substring(index, index + 2), 16); + b[i] = (byte) v; + } + return b; } } 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 837b638..7106ad2 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java @@ -23,10 +23,6 @@ public final class BackupFilesystemUtil { return backupFileNamePrefix; } - @Deprecated - public static String getBackupFileName(String backupName) { - return backupFileNamePrefix + backupName + ".zip"; - } // @Deprecated // public static String getBackupName(String backupFileName) { diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/FileIdentifierProvider.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/FileIdentifierProvider.java index 9a03371..3fbe284 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/FileIdentifierProvider.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/FileIdentifierProvider.java @@ -4,5 +4,12 @@ import java.io.File; import java.io.IOException; public interface FileIdentifierProvider { + /** + * Generate file identifier from a random file. The file is not necessarily in the object base. + * + * @param file the file. + * @return the file identifier. + * @throws IOException when an I/O error occurs. + */ T fromFile(File file) throws IOException; } diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java index cbf1bb9..c1c87e1 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256Identifier.java @@ -1,5 +1,7 @@ package com.keuin.kbackupfabric.util.backup.incremental.identifier; +import com.keuin.kbackupfabric.util.BytesUtil; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -16,20 +18,34 @@ public class Sha256Identifier extends SingleHashIdentifier { private static final int SHA256_LENGTH = 32; private static final Sha256Identifier DUMMY = new Sha256Identifier(new byte[SHA256_LENGTH]); // only for using its hash method private static final FileIdentifierProvider factory = Sha256Identifier::fromFile; + private static final String marker = "S2"; public static Sha256Identifier fromFile(File file) throws IOException { - if (!Objects.requireNonNull(file).isFile()) { + if (!file.isFile()) { throw new IllegalArgumentException("file is not a file"); } return new Sha256Identifier(DUMMY.hash(file)); } + /** + * Load sha-256 from a named file. Only used in StorageObjectLoader. + * + * @param fileName the file name. + * @return identifier. + */ + static Sha256Identifier fromFileName(String fileName) { + if (!fileName.matches(marker + "-[0-9A-Fa-f]{32}")) + return null; + String hexString = fileName.substring(marker.length() + 1); + return new Sha256Identifier(BytesUtil.hexToBytes(hexString)); + } + public static FileIdentifierProvider getFactory() { return factory; } protected Sha256Identifier(byte[] hash) { - super(hash); + super(hash, marker); Objects.requireNonNull(hash); if (hash.length != SHA256_LENGTH) { throw new IllegalStateException(String.format("SHA256 must be %d bytes", SHA256_LENGTH)); diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/SingleHashIdentifier.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/SingleHashIdentifier.java index 3b96f79..0f62f2b 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/SingleHashIdentifier.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/SingleHashIdentifier.java @@ -5,6 +5,7 @@ import com.keuin.kbackupfabric.util.BytesUtil; import java.io.File; import java.io.IOException; import java.util.Arrays; +import java.util.Objects; /** * A simple identifier based on a single hash function. @@ -13,9 +14,13 @@ import java.util.Arrays; public abstract class SingleHashIdentifier implements ObjectIdentifier { private final byte[] hash; + private final String type; - protected SingleHashIdentifier(byte[] hash) { + protected SingleHashIdentifier(byte[] hash, String type) { + Objects.requireNonNull(hash); + Objects.requireNonNull(type); this.hash = Arrays.copyOf(hash, hash.length); + this.type = type; } /** @@ -28,7 +33,7 @@ public abstract class SingleHashIdentifier implements ObjectIdentifier { @Override public String getIdentification() { - return BytesUtil.bytesToHex(hash); + return type + "-" + BytesUtil.bytesToHex(hash); } @Override @@ -38,4 +43,11 @@ public abstract class SingleHashIdentifier implements ObjectIdentifier { } return Arrays.equals(hash, ((SingleHashIdentifier) obj).hash); } + + @Override + public int hashCode() { + int result = Objects.hash(type); + result = 31 * result + Arrays.hashCode(hash); + return result; + } } diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/StorageObjectLoader.java b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/StorageObjectLoader.java new file mode 100644 index 0000000..96bc295 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/StorageObjectLoader.java @@ -0,0 +1,25 @@ +package com.keuin.kbackupfabric.util.backup.incremental.identifier; + +import java.io.File; +import java.util.Objects; + +public class StorageObjectLoader { + /** + * Get identifier from storage file. + * + * @param file storage file. + * @return identifier. If failed, return null. + */ + public static ObjectIdentifier asIdentifier(File file) { + Objects.requireNonNull(file); + String fileName = file.getName(); + ObjectIdentifier identifier; + + identifier = Sha256Identifier.fromFileName(fileName); + if (identifier != null) + return identifier; + + // Add more identifiers. + return null; + } +} 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 6c359c6..6fd339b 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 @@ -3,6 +3,8 @@ 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 com.keuin.kbackupfabric.util.backup.incremental.identifier.ObjectIdentifier; +import com.keuin.kbackupfabric.util.backup.incremental.identifier.StorageObjectLoader; import java.io.File; import java.io.FileNotFoundException; @@ -10,14 +12,15 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Map; -import java.util.Objects; +import java.util.*; import static org.apache.commons.io.FileUtils.forceDelete; public class IncrementalBackupStorageManager { private final Path backupStorageBase; + private final Map map = new HashMap<>(); + private boolean loaded = false; public IncrementalBackupStorageManager(Path backupStorageBase) { this.backupStorageBase = backupStorageBase; @@ -127,8 +130,31 @@ public class IncrementalBackupStorageManager { return copyCount; } + public int cleanUnusedObjects(Iterable collectionIterable) { + // construct object list in memory + Set objects = new HashSet<>(); +// backupStorageBase + + for (ObjectCollection collection : collectionIterable) { + for (ObjectElement ele : collection.getElementMap().values()) { + + } + } + throw new RuntimeException("not impl"); + } + + /** + * Check all objects, return unused ones. + * + * @return the unused ones. + */ + private Map markUnusedObjects() { + throw new RuntimeException("not impl"); + } + /** * Check if the backup base contains given element. + * * @param objectElement the element. * @return true or false. */ @@ -137,4 +163,26 @@ public class IncrementalBackupStorageManager { return (new File(backupStorageBase.toFile(), objectElement.getIdentifier().getIdentification())).exists(); } + private void lazyLoadStorage() throws IOException { + if (!loaded) { + loadStorage(); + loaded = true; + } + } + + private synchronized void loadStorage() throws IOException { + map.clear(); + Files.walk(backupStorageBase, 1).forEach(path -> { + File file = path.toFile(); + ObjectIdentifier identifier = StorageObjectLoader.asIdentifier(file); + if (identifier == null) { + map.clear(); + throw new IllegalStateException(String.format( + "Bad storage object %s: cannot recognize identifier.", file.getName() + )); + } + map.put(identifier, file); + }); + } + } diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java index 972403b..8ebc7ff 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/name/BackupFileNameEncoder.java @@ -18,13 +18,19 @@ public interface BackupFileNameEncoder { /** * Extract custom and backup time from backup file name. + * * @param fileName the backup file name. * @return the information. If the given file name is invalid, return null. */ BackupBasicInformation decode(String fileName); + default boolean isValidFileName(String fileName) { + return decode(fileName) != null; + } + /** * Check if the given string is a valid custom backup name. + * * @param customName the custom backup name. * @return if the name is valid. */ @@ -45,7 +51,7 @@ public interface BackupFileNameEncoder { private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm.ss"); - BackupBasicInformation(String customName, LocalDateTime time) { + protected BackupBasicInformation(String customName, LocalDateTime time) { this.customName = customName; this.time = time; } 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 3c35201..926f47c 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 @@ -24,7 +24,7 @@ public class IncrementalBackupFileNameEncoder implements BackupFileNameEncoder { @Override public BackupFileNameEncoder.BackupBasicInformation decode(String fileName) { Pattern pattern = Pattern.compile( - backupFileNamePrefix + "-" + "([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_(.+)\\.kbi" + "^" + backupFileNamePrefix + "-" + "([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_(.+)\\.kbi" + "$" ); Matcher matcher = pattern.matcher(fileName); if (matcher.find()) { diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java index bcba114..ef15ae7 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoder.java @@ -20,7 +20,7 @@ public class PrimitiveBackupFileNameEncoder implements BackupFileNameEncoder { @Override public BackupBasicInformation decode(String fileName) { Pattern pattern = Pattern.compile( - backupFileNamePrefix + "-" + "([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_(.+)\\.zip" + "^" + backupFileNamePrefix + "-" + "([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2})_(.+)\\.zip" + "$" ); Matcher matcher = pattern.matcher(fileName); if (matcher.find()) { diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/provider/AvailableBackupProvider.java b/src/main/java/com/keuin/kbackupfabric/util/backup/provider/AvailableBackupProvider.java new file mode 100644 index 0000000..caa0e84 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/provider/AvailableBackupProvider.java @@ -0,0 +1,8 @@ +package com.keuin.kbackupfabric.util.backup.provider; + +/** + * List all backup in disk. Provide their basic information as soon as possible. + */ +public class AvailableBackupProvider { + // TODO: remove obsolete impl. in command user interface. Use this instead. +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/provider/IncrementalBackupInformation.java b/src/main/java/com/keuin/kbackupfabric/util/backup/provider/IncrementalBackupInformation.java new file mode 100644 index 0000000..861d210 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/provider/IncrementalBackupInformation.java @@ -0,0 +1,13 @@ +package com.keuin.kbackupfabric.util.backup.provider; + +import com.keuin.kbackupfabric.util.backup.name.BackupFileNameEncoder; + +import java.time.LocalDateTime; + +public class IncrementalBackupInformation extends BackupFileNameEncoder.BackupBasicInformation { + // TODO: show total size for incremental backup + + public IncrementalBackupInformation(String customName, LocalDateTime time) { + super(customName, time); + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/provider/PrimitiveBackupInformation.java b/src/main/java/com/keuin/kbackupfabric/util/backup/provider/PrimitiveBackupInformation.java new file mode 100644 index 0000000..d3d2db8 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/provider/PrimitiveBackupInformation.java @@ -0,0 +1,14 @@ +package com.keuin.kbackupfabric.util.backup.provider; + +import com.keuin.kbackupfabric.util.backup.name.BackupFileNameEncoder; + +import java.time.LocalDateTime; + +public class PrimitiveBackupInformation extends BackupFileNameEncoder.BackupBasicInformation { + public final long sizeBytes; + + public PrimitiveBackupInformation(String customName, LocalDateTime time, long sizeBytes) { + super(customName, time); + this.sizeBytes = sizeBytes; + } +} diff --git a/src/test/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethodTest.java b/src/test/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethodTest.java index 7e4f267..58e1711 100644 --- a/src/test/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethodTest.java +++ b/src/test/java/com/keuin/kbackupfabric/operation/backup/method/ConfiguredIncrementalBackupMethodTest.java @@ -4,6 +4,7 @@ import org.apache.commons.codec.digest.DigestUtils; import org.junit.Test; import java.io.*; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @@ -19,14 +20,14 @@ public class ConfiguredIncrementalBackupMethodTest { private final String destDirectoryName = "destination"; private final String indexFileName = "index"; - private final double directoryFactor = 0.4; - private final double fileFactor = 0.1; - private final int maxRandomFileSizeBytes = 1024 * 1024; + private final double directoryFactor = 0.03; + private final double fileFactor = 0.05; + private final int maxRandomFileSizeBytes = 1024 * 1024 * 16; private final Function scaleDecayFunc = (x) -> x - 1; @Test public void iterationTest() throws IOException { - int a = 100; + int a = 12; for (int i = 0; i < a; ++i) { performTest(Math.min(i + 1, 10)); System.out.println("Round " + i + " passed."); @@ -75,9 +76,28 @@ public class ConfiguredIncrementalBackupMethodTest { if (!method.restore()) fail(); + boolean fake = scale % 2 != 0; + + int[] success = new int[1]; + if (fake) { + Files.walk(sourcePath).filter(path -> path.toFile().isFile()).limit(3).forEach(path -> { + if (!path.toFile().delete()) + fail(); + success[0]++; + }); + if (success[0] == 0) + fake = false; + } + + if (fake) + System.out.println("Fake: deleted " + success[0] + " file(s)."); + String hash2 = calcMD5HashForDir(sourcePath.toFile(), true); - assertEquals(hash1, hash2); + if (!fake) + assertEquals(hash1, hash2); + else + assertNotEquals(hash1, hash2); } private void createRandomDirectoryTree(String path, int scale) throws IOException { @@ -94,7 +114,7 @@ public class ConfiguredIncrementalBackupMethodTest { for (int i = 0; i < subFileCount; i++) { String subFile = null; while (subFile == null || new File(path, subFile).exists()) - subFile = getRandomString((int) (Math.random() * 16 + 1)); + subFile = getRandomString((int) (Math.random() * 16 + 5)); createRandomFile(new File(path, subFile), maxRandomFileSizeBytes); } @@ -103,7 +123,7 @@ public class ConfiguredIncrementalBackupMethodTest { for (int i = 0; i < subDirCount; i++) { String subDir = null; while (subDir == null || new File(path, subDir).exists()) - subDir = getRandomString((int) (Math.random() * 16 + 1)); + subDir = getRandomString((int) (Math.random() * 16 + 5)); createRandomDirectoryTree(new File(path, subDir).getAbsolutePath(), scaleDecayFunc.apply(scale)); } } diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java index b7e0aad..3f722a4 100644 --- a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java +++ b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/ObjectCollectionFactoryTest.java @@ -20,7 +20,7 @@ public class ObjectCollectionFactoryTest { } for (Map.Entry entry : collection.getElementMap().entrySet()) { // assertTrue(subElements.contains(e.getIdentification())); - assertEquals(subElements.get(entry.getKey()), entry.getValue().getIdentifier().getIdentification()); + assertEquals(subElements.get(entry.getKey()).toUpperCase(), entry.getValue().getIdentifier().getIdentification().toUpperCase()); } } @@ -39,14 +39,14 @@ public class ObjectCollectionFactoryTest { final Map elements = new HashMap<>(); // check root dir - elements.put("a", "261CA0D59FEE8FD169802BB8030A07CF23E5C1593FA81A16C6D0A8CF27DAA2ED"); - elements.put("b", "B3FED75012C4969DC63A50EBC4E745FF77E4A06E0B04720EF71EF033032EBAF7"); + elements.put("a", "S2-261CA0D59FEE8FD169802BB8030A07CF23E5C1593FA81A16C6D0A8CF27DAA2ED"); + elements.put("b", "S2-B3FED75012C4969DC63A50EBC4E745FF77E4A06E0B04720EF71EF033032EBAF7"); validate(collection, Arrays.asList("1", "2", "3"), elements); elements.clear(); // check `1` - elements.put("a", "E8620F35A5DB33B1257CC51245DDACDA8AF3E0D431A8A38473575E468BCBD0BD"); - elements.put("b", "19EE41585A674274891DE5A4B365DBAB9C49C576AB6F86CD515B683724D2DBBD"); + elements.put("a", "S2-E8620F35A5DB33B1257CC51245DDACDA8AF3E0D431A8A38473575E468BCBD0BD"); + elements.put("b", "S2-19EE41585A674274891DE5A4B365DBAB9C49C576AB6F86CD515B683724D2DBBD"); validate(collection.getSubCollectionMap().get("1"), Arrays.asList("11", "12"), elements); elements.clear(); @@ -60,8 +60,8 @@ public class ObjectCollectionFactoryTest { validate(collection.getSubCollectionMap().get("1").getSubCollectionMap().get("11"), Collections.singletonList("111"), Collections.emptyMap()); // check `111` - elements.put("a", "1EDBE882A757E1FAFCA77A9D3BE3FF5D2BB3E2037B238C865F1F957C431F43B4"); - elements.put("b", "30BA7CD8B4AD93A8B3826CD8D1518790924EEBB930EC04DF7DFB03A50B17D7BC"); + elements.put("a", "S2-1EDBE882A757E1FAFCA77A9D3BE3FF5D2BB3E2037B238C865F1F957C431F43B4"); + elements.put("b", "S2-30BA7CD8B4AD93A8B3826CD8D1518790924EEBB930EC04DF7DFB03A50B17D7BC"); validate( collection.getSubCollectionMap().get("1").getSubCollectionMap().get("11").getSubCollectionMap().get("111"), Collections.emptyList(), diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256IdentifierTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256IdentifierTest.java index 53e92e2..f799a95 100644 --- a/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256IdentifierTest.java +++ b/src/test/java/com/keuin/kbackupfabric/util/backup/incremental/identifier/Sha256IdentifierTest.java @@ -15,7 +15,7 @@ public class Sha256IdentifierTest { try { Sha256Identifier sha256 = Sha256Identifier.fromFile(new File("./testfile/Sha256IdentifierTest")); String str = sha256.getIdentification().toUpperCase(); - assertEquals("315F5BDB76D078C43B8AC0064E4A0164612B1FCE77C869345BFC94C75894EDD3", str); + assertEquals("S2-315F5BDB76D078C43B8AC0064E4A0164612B1FCE77C869345BFC94C75894EDD3", str); } catch (IOException e) { e.printStackTrace(); fail(); diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoderTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoderTest.java new file mode 100644 index 0000000..e5fedd7 --- /dev/null +++ b/src/test/java/com/keuin/kbackupfabric/util/backup/name/IncrementalBackupFileNameEncoderTest.java @@ -0,0 +1,38 @@ +package com.keuin.kbackupfabric.util.backup.name; + +import org.junit.Test; + +import java.time.LocalDateTime; + +import static org.junit.Assert.*; + +public class IncrementalBackupFileNameEncoderTest { + @Test + public void testEncode() { + LocalDateTime time = LocalDateTime.of(1, 1, 1, 1, 1, 1); + String customName = "name"; + IncrementalBackupFileNameEncoder encoder = new IncrementalBackupFileNameEncoder(); + assertEquals("incremental-0001-01-01_01-01-01_name.kbi", encoder.encode(customName, time)); + } + + @Test + public void testDecode() { + LocalDateTime time = LocalDateTime.of(1, 1, 1, 1, 1, 1); + String customName = "name"; + IncrementalBackupFileNameEncoder encoder = new IncrementalBackupFileNameEncoder(); + BackupFileNameEncoder.BackupBasicInformation information = encoder.decode("incremental-0001-01-01_01-01-01_name.kbi"); + assertEquals(time, information.time); + assertEquals(customName, information.customName); + } + + @Test + public void isValid() { + IncrementalBackupFileNameEncoder encoder = new IncrementalBackupFileNameEncoder(); + assertTrue(encoder.isValidFileName("incremental-0001-01-01_01-01-01_name.kbi")); + assertTrue(encoder.isValidFileName("incremental-0001-01-01_01-01-01_0001-01-01_01-01-01_name.kbi")); + assertFalse(encoder.isValidFileName("incremental-0001-01-01_01-01-01incremental-0001-01-01_01-01-01_name.kbi")); + assertFalse(encoder.isValidFileName("incremental-0001-01-01_01-01-01_name")); + assertFalse(encoder.isValidFileName("incremental-0001-01-01_01-01-01_name.zip")); + assertFalse(encoder.isValidFileName("somefile")); + } +} \ No newline at end of file diff --git a/src/test/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoderTest.java b/src/test/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoderTest.java index 4823575..abc19d5 100644 --- a/src/test/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoderTest.java +++ b/src/test/java/com/keuin/kbackupfabric/util/backup/name/PrimitiveBackupFileNameEncoderTest.java @@ -5,17 +5,46 @@ import org.junit.Test; import java.time.LocalDateTime; import java.time.ZoneOffset; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; public class PrimitiveBackupFileNameEncoderTest { @Test public void testConsistency() { - LocalDateTime time = LocalDateTime.ofEpochSecond(System.currentTimeMillis()/1000, 0, ZoneOffset.UTC); + LocalDateTime time = LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, ZoneOffset.UTC); String name = "Test Na_me"; PrimitiveBackupFileNameEncoder encoder = new PrimitiveBackupFileNameEncoder(); BackupFileNameEncoder.BackupBasicInformation information = encoder.decode(encoder.encode(name, time)); assertEquals(time, information.time); assertEquals(name, information.customName); } + + @Test + public void testEncode() { + LocalDateTime time = LocalDateTime.of(1, 1, 1, 1, 1, 1); + String customName = "name"; + PrimitiveBackupFileNameEncoder encoder = new PrimitiveBackupFileNameEncoder(); + assertEquals("kbackup-0001-01-01_01-01-01_name.zip", encoder.encode(customName, time)); + } + + @Test + public void testDecode() { + LocalDateTime time = LocalDateTime.of(1, 1, 1, 1, 1, 1); + String customName = "name"; + PrimitiveBackupFileNameEncoder encoder = new PrimitiveBackupFileNameEncoder(); + BackupFileNameEncoder.BackupBasicInformation information = encoder.decode("kbackup-0001-01-01_01-01-01_name.zip"); + assertEquals(time, information.time); + assertEquals(customName, information.customName); + } + + @Test + public void isValid() { + PrimitiveBackupFileNameEncoder encoder = new PrimitiveBackupFileNameEncoder(); + assertTrue(encoder.isValidFileName("kbackup-0001-01-01_01-01-01_name.zip")); + assertTrue(encoder.isValidFileName("kbackup-0001-01-01_01-01-01_0001-01-01_01-01-01_name.zip")); + assertFalse(encoder.isValidFileName("kbackup-0001-01-01_01-01-01kbackup-0001-01-01_01-01-01_name.zip")); + assertFalse(encoder.isValidFileName("kbackup-0001-01-01_01-01-01_name")); + assertFalse(encoder.isValidFileName("kbackup-0001-01-01_01-01-01_name.kbi")); + assertFalse(encoder.isValidFileName("somefile")); + } } \ No newline at end of file -- cgit v1.2.3