From 08ac6aaea58b7309ea95a12ca6227a6ca3ce4db5 Mon Sep 17 00:00:00 2001 From: Keuin Date: Mon, 25 Jan 2021 15:25:34 +0800 Subject: Improve user interaction. Code refactor. --- .../java/com/keuin/kbackupfabric/KBCommands.java | 455 ------------------ .../keuin/kbackupfabric/KBCommandsRegister.java | 1 + .../com/keuin/kbackupfabric/KBPluginEvents.java | 11 +- .../manager/IncrementalBackupStorageManager.java | 3 + .../com/keuin/kbackupfabric/ui/BackupInfo.java | 34 ++ .../com/keuin/kbackupfabric/ui/BackupManager.java | 64 +++ .../kbackupfabric/ui/IncrementalBackupInfo.java | 68 +++ .../com/keuin/kbackupfabric/ui/KBCommands.java | 514 +++++++++++++++++++++ .../kbackupfabric/ui/PrimitiveBackupInfo.java | 78 ++++ .../com/keuin/kbackupfabric/util/DateUtil.java | 13 + .../keuin/kbackupfabric/util/FilesystemUtil.java | 11 +- 11 files changed, 789 insertions(+), 463 deletions(-) delete mode 100644 src/main/java/com/keuin/kbackupfabric/KBCommands.java create mode 100644 src/main/java/com/keuin/kbackupfabric/ui/BackupInfo.java create mode 100644 src/main/java/com/keuin/kbackupfabric/ui/BackupManager.java create mode 100644 src/main/java/com/keuin/kbackupfabric/ui/IncrementalBackupInfo.java create mode 100644 src/main/java/com/keuin/kbackupfabric/ui/KBCommands.java create mode 100644 src/main/java/com/keuin/kbackupfabric/ui/PrimitiveBackupInfo.java (limited to 'src/main/java') diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/KBCommands.java deleted file mode 100644 index fe5ea7f..0000000 --- a/src/main/java/com/keuin/kbackupfabric/KBCommands.java +++ /dev/null @@ -1,455 +0,0 @@ -package com.keuin.kbackupfabric; - -import com.keuin.kbackupfabric.backup.BackupFilesystemUtil; -import com.keuin.kbackupfabric.backup.incremental.serializer.IncBackupInfoSerializer; -import com.keuin.kbackupfabric.backup.incremental.serializer.SavedIncrementalBackup; -import com.keuin.kbackupfabric.backup.name.IncrementalBackupFileNameEncoder; -import com.keuin.kbackupfabric.backup.name.PrimitiveBackupFileNameEncoder; -import com.keuin.kbackupfabric.backup.suggestion.BackupNameSuggestionProvider; -import com.keuin.kbackupfabric.metadata.MetadataHolder; -import com.keuin.kbackupfabric.operation.BackupOperation; -import com.keuin.kbackupfabric.operation.DeleteOperation; -import com.keuin.kbackupfabric.operation.RestoreOperation; -import com.keuin.kbackupfabric.operation.abstracts.i.Invokable; -import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod; -import com.keuin.kbackupfabric.operation.backup.method.ConfiguredIncrementalBackupMethod; -import com.keuin.kbackupfabric.operation.backup.method.ConfiguredPrimitiveBackupMethod; -import com.keuin.kbackupfabric.util.DateUtil; -import com.keuin.kbackupfabric.util.PrintUtil; -import com.mojang.brigadier.arguments.StringArgumentType; -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 java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; - -import static com.keuin.kbackupfabric.backup.BackupFilesystemUtil.*; -import static com.keuin.kbackupfabric.util.PrintUtil.*; - -public final class KBCommands { - - - private static final int SUCCESS = 1; - private static final int FAILED = -1; - private static final String DEFAULT_BACKUP_NAME = "noname"; - private static boolean notifiedPreviousRestoration = false; - - //private static final Logger LOGGER = LogManager.getLogger(); - - private static final List backupFileNameList = new ArrayList<>(); // index -> backupName - private static Invokable pendingOperation = null; - //private static BackupMethod activatedBackupMethod = new PrimitiveBackupMethod(); // The backup method we currently using - - /** - * Print the help menu. - * - * @param context the context. - * @return stat code. - */ - public static int help(CommandContext context) { - msgInfo(context, "==== KBackup Manual ===="); - msgInfo(context, "/kb , /kb help - Print help menu."); - msgInfo(context, "/kb list - Show all backups."); - msgInfo(context, "/kb backup [incremental/zip] [backup_name] - Backup the whole level to backup_name. The default name is current system time."); - msgInfo(context, "/kb restore - Delete the whole current level and restore from given backup. /kb restore is identical with /kb list."); - msgInfo(context, "/kb confirm - Confirm and start restoring."); - msgInfo(context, "/kb cancel - Cancel the restoration to be confirmed. If cancelled, /kb confirm will not run."); - return SUCCESS; - } - - /** - * Print the help menu. (May show extra info during the first run after restoring) - * - * @param context the context. - * @return stat code. - */ - public static int kb(CommandContext context) { - int statCode = list(context); - if (MetadataHolder.hasMetadata() && !notifiedPreviousRestoration) { - // Output metadata info - notifiedPreviousRestoration = true; - msgStress(context, "Restored from backup " + MetadataHolder.getMetadata().getBackupName()); - } - return statCode; - } - - /** - * List all existing backups. - * - * @param context the context. - * @return stat code. - */ - public static int list(CommandContext context) { - // lazy: it just works as expected. Don't try to refactor, it's a waste of time. Just improve display and - // that's enough. - // TODO: Show real name and size and etc info for incremental backup - // TODO: Show concrete info from metadata for `.zip` backup - MinecraftServer server = context.getSource().getMinecraftServer(); - // TODO: refactor this to use {@link ObjectCollectionSerializer#fromDirectory} - File[] files = getBackupSaveDirectory(server).listFiles( - (dir, name) -> dir.isDirectory() && - (name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix()) - || name.toLowerCase().endsWith(".kbi")) - ); - - Function backupInformationProvider = file -> { - Objects.requireNonNull(file); - if (file.getName().toLowerCase().endsWith(".zip")) - return getPrimitiveBackupInformationString(file.getName(), file.length()); - // TODO: refactor this to use {@link ObjectCollectionSerializer#fromDirectory} - else if (file.getName().toLowerCase().endsWith(".kbi")) - return getIncrementalBackupInformationString(file); - return file.getName(); - }; - - 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, backupInformationProvider.apply(file))); - backupFileNameList.add(backupFileName); - } - } else { - msgErr(context, "Error: failed to list files in backup folder."); - } - } - return SUCCESS; - } - - /** - * Backup with context parameter backupName. - * - * @param context the context. - * @return stat code. - */ - public static int primitiveBackup(CommandContext context) { - //KBMain.backup("name") - String customBackupName = StringArgumentType.getString(context, "backupName"); - if (customBackupName.matches("[0-9]*")) { - // Numeric param is not allowed - customBackupName = String.format("a%s", customBackupName); - msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", customBackupName)); - } - return doBackup(context, customBackupName, false); - } - - /** - * Backup with default name. - * - * @param context the context. - * @return stat code. - */ - public static int primitiveBackupWithDefaultName(CommandContext context) { - return doBackup(context, DEFAULT_BACKUP_NAME, false); - } - - public static int incrementalBackup(CommandContext context) { - String customBackupName = StringArgumentType.getString(context, "backupName"); - if (customBackupName.matches("[0-9]*")) { - // Numeric param is not allowed - customBackupName = String.format("a%s", customBackupName); - msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", customBackupName)); - } - return doBackup(context, customBackupName, true); - } - - public static int incrementalBackupWithDefaultName(CommandContext context) { - return doBackup(context, DEFAULT_BACKUP_NAME, true); - } - - -// public static int incrementalBackup(CommandContext context) { -// //KBMain.backup("name") -// String backupName = StringArgumentType.getString(context, "backupName"); -// if (backupName.matches("[0-9]*")) { -// // Numeric param is not allowed -// backupName = String.format("a%s", backupName); -// msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", backupName)); -// } -// return doBackup(context, backupName, IncrementalBackupMethod.getInstance()); -// } -// -// public static int incrementalBackupWithDefaultName(CommandContext context) { -// return doBackup(context, DEFAULT_BACKUP_NAME, IncrementalBackupMethod.getInstance()); -// } - - /** - * Delete an existing backup with context parameter backupName. - * Simply set the pending backupName to given backupName, for the second confirmation. - * - * @param context the context. - * @return stat code. - */ - public static int delete(CommandContext context) { - - String backupFileName = parseBackupFileName(context, StringArgumentType.getString(context, "backupName")); - MinecraftServer server = context.getSource().getMinecraftServer(); - - if (backupFileName == null) - return list(context); // Show the list and return - - // Validate backupName - if (!isBackupFileExists(backupFileName, server)) { - // Invalid backupName - msgErr(context, "Invalid backup name! Please check your input. The list index number is also valid."); - return FAILED; - } - - // Update pending task - //pendingOperation = AbstractConfirmableOperation.createDeleteOperation(context, backupName); - pendingOperation = new DeleteOperation(context, backupFileName); - - msgWarn(context, String.format("DELETION WARNING: The deletion is irreversible! You will lose the backup %s permanently. Use /kb confirm to start or /kb cancel to abort.", backupFileName), true); - return SUCCESS; - } - - - /** - * Restore with context parameter backupName. - * Simply set the pending backupName to given backupName, for the second confirmation. - * - * @param context the context. - * @return stat code. - */ - public static int restore(CommandContext context) { - try { - //KBMain.restore("name") - MinecraftServer server = context.getSource().getMinecraftServer(); - String backupFileName = parseBackupFileName(context, StringArgumentType.getString(context, "backupName")); - backupFileName = parseBackupFileName(context, backupFileName); - - if (backupFileName == null) - return list(context); // Show the list and return - - // Validate backupName - if (!isBackupFileExists(backupFileName, server)) { - // Invalid backupName - msgErr(context, "Invalid backup name! Please check your input. The list index number is also valid.", false); - return FAILED; - } - - // Detect backup type - - - // Update pending task - //pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName); -// File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName)); - // TODO: improve this - ConfiguredBackupMethod method = backupFileName.endsWith(".zip") ? - new ConfiguredPrimitiveBackupMethod( - backupFileName, getLevelPath(server), getBackupSaveDirectory(server).getAbsolutePath() - ) : new ConfiguredIncrementalBackupMethod( - backupFileName, getLevelPath(server), - getBackupSaveDirectory(server).getAbsolutePath(), - getIncrementalBackupBaseDirectory(server).getAbsolutePath() - ); - // String backupSavePath, String levelPath, String backupFileName -// getBackupSaveDirectory(server).getAbsolutePath(), getLevelPath(server), backupFileName - pendingOperation = new RestoreOperation(context, method); - - msgWarn(context, String.format("RESET WARNING: You will LOSE YOUR CURRENT WORLD PERMANENTLY! The worlds will be replaced with backup %s . Use /kb confirm to start or /kb cancel to abort.", backupFileName), true); - return SUCCESS; - } catch (IOException e) { - msgErr(context, String.format("An I/O exception occurred while making backup: %s", e)); - } - return FAILED; - } - - private static int doBackup(CommandContext context, String customBackupName, boolean incremental) { - try { - // Real backup name (compatible with legacy backup): date_name, such as 2020-04-23_21-03-00_test - //KBMain.backup("name") -// String backupName = BackupNameTimeFormatter.getTimeString() + "_" + customBackupName; - - // Validate file name - final char[] ILLEGAL_CHARACTERS = {'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':'}; - for (char c : ILLEGAL_CHARACTERS) { - if (customBackupName.contains(String.valueOf(c))) { - msgErr(context, String.format("Name cannot contain special character \"%c\".", c)); - return FAILED; - } - } - - PrintUtil.info("Start backup..."); - - // configure backup method - MinecraftServer server = context.getSource().getMinecraftServer(); - ConfiguredBackupMethod method = !incremental ? new ConfiguredPrimitiveBackupMethod( - PrimitiveBackupFileNameEncoder.INSTANCE.encode(customBackupName, LocalDateTime.now()), - getLevelPath(server), - getBackupSaveDirectory(server).getCanonicalPath() - ) : new ConfiguredIncrementalBackupMethod( - IncrementalBackupFileNameEncoder.INSTANCE.encode(customBackupName, LocalDateTime.now()), - getLevelPath(server), - getBackupSaveDirectory(server).getCanonicalPath(), - getIncrementalBackupBaseDirectory(server).getCanonicalPath() - ); - - // dispatch to operation worker - BackupOperation operation = new BackupOperation(context, method); - if (operation.invoke()) { - return SUCCESS; - } else if (operation.isBlocked()) { - msgWarn(context, "Another task is running, cannot issue new backup at once."); - return FAILED; - } - } catch (IOException e) { - msgErr(context, String.format("An I/O exception occurred while making backup: %s", e)); - } - return FAILED; - } - - /** - * Restore with context parameter backupName. - * - * @param context the context. - * @return stat code. - */ - public static int confirm(CommandContext context) { - if (pendingOperation == null) { - msgWarn(context, "Nothing to confirm."); - return FAILED; - } - - Invokable operation = pendingOperation; - pendingOperation = null; - - boolean returnValue = operation.invoke(); - - // By the way, update suggestion list. - BackupNameSuggestionProvider.updateCandidateList(); - - return returnValue ? SUCCESS : FAILED; // block compiler's complain. - } - - /** - * Cancel the execution to be confirmed. - * - * @param context the context. - * @return stat code. - */ - public static int cancel(CommandContext context) { - if (pendingOperation != null) { - PrintUtil.msgInfo(context, String.format("The %s has been cancelled.", pendingOperation.toString()), true); - pendingOperation = null; - return SUCCESS; - } else { - msgErr(context, "Nothing to cancel."); - return FAILED; - } - } - - /** - * Show the most recent backup. - * If there is no available backup, print specific info. - * - * @param context the context. - * @return stat code. - */ - public static int prev(CommandContext context) { - // FIXME: This breaks after adding incremental backup - try { - // List all backups - MinecraftServer server = context.getSource().getMinecraftServer(); - List files = Arrays.asList(Objects.requireNonNull(getBackupSaveDirectory(server).listFiles())); - files.removeIf(f -> !f.getName().startsWith(BackupFilesystemUtil.getBackupFileNamePrefix())); - files.sort((x, y) -> (int) (BackupFilesystemUtil.getBackupTimeFromBackupFileName(y.getName()) - BackupFilesystemUtil.getBackupTimeFromBackupFileName(x.getName()))); - File prevBackupFile = files.get(0); - String backupFileName = prevBackupFile.getName(); - 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", - i, - getPrimitiveBackupInformationString(backupFileName, prevBackupFile.length()) - )); - } catch (NullPointerException e) { - msgInfo(context, "There are no backups available."); - } catch (SecurityException ignored) { - msgErr(context, "Failed to read file."); - return FAILED; - } - return SUCCESS; - } - - private static String getPrimitiveBackupInformationString(String backupFileName, long backupFileSizeBytes) { - return String.format( - "(ZIP) %s , size: %s", - PrimitiveBackupFileNameEncoder.INSTANCE.decode(backupFileName), - getFriendlyFileSizeString(backupFileSizeBytes) - ); - } - - private static String getIncrementalBackupInformationString(File backupFile) { - try { - SavedIncrementalBackup info = IncBackupInfoSerializer.fromFile(backupFile); - return "(Incremental) " + info.getBackupName() - + ", " + DateUtil.getString(info.getBackupTime()) - + ((info.getTotalSizeBytes() > 0) ? - (" size: " + BackupFilesystemUtil.getFriendlyFileSizeString(info.getTotalSizeBytes())) : ""); - } catch (IOException e) { - e.printStackTrace(); - return "(Incremental) " + backupFile.getName(); - } - } - -// /** -// * Select the backup method we use. -// * @param context the context. -// * @return stat code. -// */ -// public static int setMethod(CommandContext context) { -// String desiredMethodName = StringArgumentType.getString(context, "backupMethod"); -// List backupMethods = Arrays.asList(BackupType.PRIMITIVE_ZIP_BACKUP, BackupType.OBJECT_TREE_BACKUP); -// for (BackupType method : backupMethods) { -// if(method.getName().equals(desiredMethodName)) { -// // Incremental backup -//// activatedBackupMethod = -// msgInfo(context, String.format("Backup method is set to: %s", desiredMethodName)); -// return SUCCESS; -// } -// } -// -// return SUCCESS; -// } - - - private static String parseBackupFileName(CommandContext context, String userInput) { - try { - String backupName = StringArgumentType.getString(context, "backupName"); - - if (backupName.matches("[0-9]*")) { - // If numeric input - int index = Integer.parseInt(backupName) - 1; - synchronized (backupFileNameList) { - return backupFileNameList.get(index); // Replace input number with real backup file name. - } - } - } catch (NumberFormatException | IndexOutOfBoundsException ignored) { - } - return userInput; - } -} diff --git a/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java b/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java index b9baf44..a27753f 100644 --- a/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java +++ b/src/main/java/com/keuin/kbackupfabric/KBCommandsRegister.java @@ -1,6 +1,7 @@ package com.keuin.kbackupfabric; import com.keuin.kbackupfabric.backup.suggestion.BackupNameSuggestionProvider; +import com.keuin.kbackupfabric.ui.KBCommands; import com.keuin.kbackupfabric.util.PermissionValidator; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; diff --git a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java index e1d5cbd..8992d17 100644 --- a/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java +++ b/src/main/java/com/keuin/kbackupfabric/KBPluginEvents.java @@ -4,6 +4,8 @@ import com.keuin.kbackupfabric.backup.BackupFilesystemUtil; import com.keuin.kbackupfabric.backup.suggestion.BackupNameSuggestionProvider; import com.keuin.kbackupfabric.metadata.BackupMetadata; import com.keuin.kbackupfabric.metadata.MetadataHolder; +import com.keuin.kbackupfabric.ui.KBCommands; +import com.keuin.kbackupfabric.util.DateUtil; import com.keuin.kbackupfabric.util.PrintUtil; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.server.ServerStartCallback; @@ -15,8 +17,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; -import java.text.SimpleDateFormat; -import java.util.Date; import static org.apache.commons.io.FileUtils.forceDelete; @@ -39,11 +39,14 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback public void onStartServer(MinecraftServer server) { if (!(server instanceof MinecraftDedicatedServer)) - throw new RuntimeException("KBackup is a server-side-only plugin. Please do not use it in Minecraft client."); + throw new RuntimeException("KBackup is a server-side-only plugin. Please do not use it in client-side."); // Initialize player manager reference PrintUtil.setPlayerManager(server.getPlayerManager()); + // Initialize backup manager server reference + KBCommands.setServer(server); + // Update backup suggestion list BackupNameSuggestionProvider.setBackupSaveDirectory(BackupFilesystemUtil.getBackupSaveDirectory(server).getPath()); @@ -64,7 +67,7 @@ public final class KBPluginEvents implements ModInitializer, ServerStartCallback MetadataHolder.setMetadata(metadata); PrintUtil.info("Restored world from a previous backup:"); PrintUtil.info("Backup Name: " + metadata.getBackupName()); - PrintUtil.info("Create Time: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date(metadata.getBackupTime()))); + PrintUtil.info("Create Time: " + DateUtil.fromEpochMillis(metadata.getBackupTime())); // Delete metadata file if (!metadataFile.delete()) { diff --git a/src/main/java/com/keuin/kbackupfabric/backup/incremental/manager/IncrementalBackupStorageManager.java b/src/main/java/com/keuin/kbackupfabric/backup/incremental/manager/IncrementalBackupStorageManager.java index ff75d8c..0b15a84 100644 --- a/src/main/java/com/keuin/kbackupfabric/backup/incremental/manager/IncrementalBackupStorageManager.java +++ b/src/main/java/com/keuin/kbackupfabric/backup/incremental/manager/IncrementalBackupStorageManager.java @@ -18,6 +18,9 @@ import java.util.logging.Logger; import static org.apache.commons.io.FileUtils.forceDelete; +/** + * Managing the base storing all collection objects. + */ public class IncrementalBackupStorageManager { private final Path backupStorageBase; diff --git a/src/main/java/com/keuin/kbackupfabric/ui/BackupInfo.java b/src/main/java/com/keuin/kbackupfabric/ui/BackupInfo.java new file mode 100644 index 0000000..cf537f9 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/ui/BackupInfo.java @@ -0,0 +1,34 @@ +package com.keuin.kbackupfabric.ui; + +import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod; +import com.keuin.kbackupfabric.util.DateUtil; +import net.minecraft.server.MinecraftServer; + +import java.time.LocalDateTime; + +/** + * Used in UI part. Holds necessary information for displaying a backup. + */ +public interface BackupInfo { + String getName(); + + LocalDateTime getCreationTime(); + + long getSizeBytes(); + + String getType(); + + default String getCanonicalName() { + return getName() + "-" + DateUtil.getString(getCreationTime()); + } + + ConfiguredBackupMethod createConfiguredBackupMethod(MinecraftServer server); + + /** + * This is depreciated. But eliminating all usages needs to refactor the UI code. + * + * @return the backup file name. + */ + @Deprecated + String getBackupFileName(); +} \ No newline at end of file diff --git a/src/main/java/com/keuin/kbackupfabric/ui/BackupManager.java b/src/main/java/com/keuin/kbackupfabric/ui/BackupManager.java new file mode 100644 index 0000000..abd7d5b --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/ui/BackupManager.java @@ -0,0 +1,64 @@ +package com.keuin.kbackupfabric.ui; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Iterator; + +/** + * Holding all types of backups for the user interaction logic. + */ +public class BackupManager { + + private final File backupStorageDirectory; + + public BackupManager(File backupStorageDirectory) { + this.backupStorageDirectory = backupStorageDirectory; + } + + public BackupManager(String backupStorageDirectory) { + this.backupStorageDirectory = new File(backupStorageDirectory); + } + + /** + * Get available backups in the disk. + * + * @return all backups. + */ + public Iterable getAllBackups() { + return new Iterable() { + @NotNull + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator fileIterator = Arrays.stream(backupStorageDirectory.listFiles()).filter(file -> { + String name = file.getName().toLowerCase(); + return name.endsWith(".zip") || name.endsWith(".kbi"); + }).iterator(); + + @Override + public boolean hasNext() { + return fileIterator.hasNext(); + } + + @Override + public BackupInfo next() { + try { + File backupFile = fileIterator.next(); + String fileName = backupFile.getName().toLowerCase(); + if (fileName.endsWith(".zip")) + return PrimitiveBackupInfo.fromFile(backupFile); + if (fileName.endsWith(".kbi")) + return IncrementalBackupInfo.fromFile(backupFile); + throw new RuntimeException("Invalid backup file extname"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + } + }; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/ui/IncrementalBackupInfo.java b/src/main/java/com/keuin/kbackupfabric/ui/IncrementalBackupInfo.java new file mode 100644 index 0000000..89d7fbf --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/ui/IncrementalBackupInfo.java @@ -0,0 +1,68 @@ +package com.keuin.kbackupfabric.ui; + +import com.keuin.kbackupfabric.backup.incremental.serializer.IncBackupInfoSerializer; +import com.keuin.kbackupfabric.backup.incremental.serializer.SavedIncrementalBackup; +import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod; +import net.minecraft.server.MinecraftServer; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneId; + +/** + * Used in UI part. + */ +public class IncrementalBackupInfo implements BackupInfo { + private final String name; + private final LocalDateTime creationTime; + private final long sizeBytes; + private final String fileName; + + private IncrementalBackupInfo(String name, LocalDateTime creationTime, long sizeBytes, String fileName) { + this.name = name; + this.creationTime = creationTime; + this.sizeBytes = sizeBytes; + this.fileName = fileName; + } + + public static IncrementalBackupInfo fromFile(File indexFile) throws IOException { + SavedIncrementalBackup info = IncBackupInfoSerializer.fromFile(indexFile); + return new IncrementalBackupInfo( + info.getBackupName(), + info.getBackupTime().withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime(), + info.getTotalSizeBytes(), + indexFile.getName() + ); + } + + @Override + public String getName() { + return name; + } + + @Override + public LocalDateTime getCreationTime() { + return creationTime; + } + + @Override + public long getSizeBytes() { + return sizeBytes; + } + + @Override + public String getType() { + return "Incremental"; + } + + @Override + public ConfiguredBackupMethod createConfiguredBackupMethod(MinecraftServer server) { + throw new RuntimeException("not implemented"); + } + + @Override + public String getBackupFileName() { + return fileName; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/ui/KBCommands.java b/src/main/java/com/keuin/kbackupfabric/ui/KBCommands.java new file mode 100644 index 0000000..87f667d --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/ui/KBCommands.java @@ -0,0 +1,514 @@ +package com.keuin.kbackupfabric.ui; + +import com.keuin.kbackupfabric.backup.BackupFilesystemUtil; +import com.keuin.kbackupfabric.backup.name.IncrementalBackupFileNameEncoder; +import com.keuin.kbackupfabric.backup.name.PrimitiveBackupFileNameEncoder; +import com.keuin.kbackupfabric.backup.suggestion.BackupNameSuggestionProvider; +import com.keuin.kbackupfabric.metadata.MetadataHolder; +import com.keuin.kbackupfabric.operation.BackupOperation; +import com.keuin.kbackupfabric.operation.DeleteOperation; +import com.keuin.kbackupfabric.operation.RestoreOperation; +import com.keuin.kbackupfabric.operation.abstracts.i.Invokable; +import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod; +import com.keuin.kbackupfabric.operation.backup.method.ConfiguredIncrementalBackupMethod; +import com.keuin.kbackupfabric.operation.backup.method.ConfiguredPrimitiveBackupMethod; +import com.keuin.kbackupfabric.util.DateUtil; +import com.keuin.kbackupfabric.util.PrintUtil; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import static com.keuin.kbackupfabric.backup.BackupFilesystemUtil.*; +import static com.keuin.kbackupfabric.util.PrintUtil.*; + +public final class KBCommands { + + + private static final int SUCCESS = 1; + private static final int FAILED = -1; + private static final String DEFAULT_BACKUP_NAME = "noname"; + private static boolean notifiedPreviousRestoration = false; + + // don't access it directly + private static MinecraftServer server; + private static BackupManager backupManager; + private static final Object managerCreatorLock = new Object(); + + //private static final Logger LOGGER = LogManager.getLogger(); + + private static final List backupList = new ArrayList<>(); // index -> backupName + private static Invokable pendingOperation = null; + //private static BackupMethod activatedBackupMethod = new PrimitiveBackupMethod(); // The backup method we currently using + + public static void setServer(MinecraftServer server) { + KBCommands.server = server; + } + + private static MinecraftServer getServer() { + if (server != null) + return server; + throw new IllegalStateException("server is not initialized."); + } + + private static BackupManager getBackupManager() { + synchronized (managerCreatorLock) { + if (backupManager == null) + backupManager = new BackupManager(getBackupSaveDirectory(getServer())); + return backupManager; + } + } + + /** + * Print the help menu. + * + * @param context the context. + * @return stat code. + */ + public static int help(CommandContext context) { + msgInfo(context, "==== KBackup Manual ===="); + msgInfo(context, "/kb , /kb help - Print help menu."); + msgInfo(context, "/kb list - Show all backups."); + msgInfo(context, "/kb backup [incremental/zip] [backup_name] - Backup the whole level to backup_name. The default name is current system time."); + msgInfo(context, "/kb restore - Delete the whole current level and restore from given backup. /kb restore is identical with /kb list."); + msgInfo(context, "/kb confirm - Confirm and start restoring."); + msgInfo(context, "/kb cancel - Cancel the restoration to be confirmed. If cancelled, /kb confirm will not run."); + return SUCCESS; + } + + /** + * Print the help menu. (May show extra info during the first run after restoring) + * + * @param context the context. + * @return stat code. + */ + public static int kb(CommandContext context) { + int statCode = list(context); + if (MetadataHolder.hasMetadata() && !notifiedPreviousRestoration) { + // Output metadata info + notifiedPreviousRestoration = true; + msgStress(context, "Restored from backup " + + MetadataHolder.getMetadata().getBackupName() + " (created at " + + DateUtil.fromEpochMillis(MetadataHolder.getMetadata().getBackupTime()) + + ")"); + } + return statCode; + } + + private static void updateBackupList() { + synchronized (backupList) { + backupList.clear(); + List list = new ArrayList<>(); + getBackupManager().getAllBackups().forEach(list::add); + list.sort(Comparator.comparing(BackupInfo::getCreationTime).reversed()); + backupList.addAll(list); + } + } + + /** + * List all existing backups. + * + * @param context the context. + * @return stat code. + */ + public static int list(CommandContext context) { + // lazy: it just works as expected. Don't try to refactor, it's a waste of time. Just improve display and + // that's enough. + // TODO: Show real name and size and etc info for incremental backup + // TODO: Show concrete info from metadata for `.zip` backup +// MinecraftServer server = context.getSource().getMinecraftServer(); + // TODO: refactor this to use {@link ObjectCollectionSerializer#fromDirectory} +// File[] files = getBackupSaveDirectory(server).listFiles( +// (dir, name) -> dir.isDirectory() && +// (name.toLowerCase().endsWith(".zip") && name.toLowerCase().startsWith(getBackupFileNamePrefix()) +// || name.toLowerCase().endsWith(".kbi")) +// ); + +// Function backupInformationProvider = file -> { +// Objects.requireNonNull(file); +// if (file.getName().toLowerCase().endsWith(".zip")) +// return getPrimitiveBackupInformationString(file.getName(), file.length()); +// // TODO: refactor this to use {@link ObjectCollectionSerializer#fromDirectory} +// else if (file.getName().toLowerCase().endsWith(".kbi")) +// return getIncrementalBackupInformationString(file); +// return file.getName(); +// }; + + updateBackupList(); + synchronized (backupList) { + if (backupList.isEmpty()) + msgInfo(context, "There is no backup available. To make a new backup, use `/kb backup`."); + else + msgInfo(context, "Available backups:"); + for (int i = backupList.size() - 1; i >= 0; --i) { + BackupInfo info = backupList.get(i); + printBackupInfo(context, info, i); + } +// 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, backupInformationProvider.apply(file))); +// backupFileNameList.add(backupFileName); +// } +// } else { +// msgErr(context, "Error: failed to list files in backup folder."); +// } + } + return SUCCESS; + } + + /** + * Print backup information. + * + * @param context the context. + * @param info the info. + * @param i the index, starting from 0. + */ + private static void printBackupInfo(CommandContext context, BackupInfo info, int i) { + msgInfo(context, String.format( + "[%d] (%s) %s (%s) %s", + i + 1, + info.getType(), + info.getName(), + DateUtil.getPrettyString(info.getCreationTime()), + (info.getSizeBytes() > 0) ? BackupFilesystemUtil.getFriendlyFileSizeString(info.getSizeBytes()) : "" + )); + } + + /** + * Backup with context parameter backupName. + * + * @param context the context. + * @return stat code. + */ + public static int primitiveBackup(CommandContext context) { + //KBMain.backup("name") + String customBackupName = StringArgumentType.getString(context, "backupName"); + if (customBackupName.matches("[0-9]*")) { + // Numeric param is not allowed + customBackupName = String.format("a%s", customBackupName); + msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", customBackupName)); + } + return doBackup(context, customBackupName, false); + } + + /** + * Backup with default name. + * + * @param context the context. + * @return stat code. + */ + public static int primitiveBackupWithDefaultName(CommandContext context) { + return doBackup(context, DEFAULT_BACKUP_NAME, false); + } + + public static int incrementalBackup(CommandContext context) { + String customBackupName = StringArgumentType.getString(context, "backupName"); + if (customBackupName.matches("[0-9]*")) { + // Numeric param is not allowed + customBackupName = String.format("a%s", customBackupName); + msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", customBackupName)); + } + return doBackup(context, customBackupName, true); + } + + public static int incrementalBackupWithDefaultName(CommandContext context) { + return doBackup(context, DEFAULT_BACKUP_NAME, true); + } + + +// public static int incrementalBackup(CommandContext context) { +// //KBMain.backup("name") +// String backupName = StringArgumentType.getString(context, "backupName"); +// if (backupName.matches("[0-9]*")) { +// // Numeric param is not allowed +// backupName = String.format("a%s", backupName); +// msgWarn(context, String.format("Pure numeric name is not allowed. Renaming to %s", backupName)); +// } +// return doBackup(context, backupName, IncrementalBackupMethod.getInstance()); +// } +// +// public static int incrementalBackupWithDefaultName(CommandContext context) { +// return doBackup(context, DEFAULT_BACKUP_NAME, IncrementalBackupMethod.getInstance()); +// } + + /** + * Delete an existing backup with context parameter backupName. + * Simply set the pending backupName to given backupName, for the second confirmation. + * + * @param context the context. + * @return stat code. + */ + public static int delete(CommandContext context) { + + String backupFileName = parseBackupFileName(context, StringArgumentType.getString(context, "backupName")); + MinecraftServer server = context.getSource().getMinecraftServer(); + + if (backupFileName == null) + return list(context); // Show the list and return + + // Validate backupName + if (!isBackupFileExists(backupFileName, server)) { + // Invalid backupName + msgErr(context, "Invalid backup name! Please check your input. The list index number is also valid."); + return FAILED; + } + + // Update pending task + //pendingOperation = AbstractConfirmableOperation.createDeleteOperation(context, backupName); + pendingOperation = new DeleteOperation(context, backupFileName); + + msgWarn(context, String.format("DELETION WARNING: The deletion is irreversible! You will lose the backup %s permanently. Use /kb confirm to start or /kb cancel to abort.", backupFileName), true); + return SUCCESS; + } + + + /** + * Restore with context parameter backupName. + * Simply set the pending backupName to given backupName, for the second confirmation. + * + * @param context the context. + * @return stat code. + */ + public static int restore(CommandContext context) { + try { + //KBMain.restore("name") + MinecraftServer server = context.getSource().getMinecraftServer(); + String backupFileName = parseBackupFileName(context, StringArgumentType.getString(context, "backupName")); + backupFileName = parseBackupFileName(context, backupFileName); + + if (backupFileName == null) + return list(context); // Show the list and return + + // Validate backupName + if (!isBackupFileExists(backupFileName, server)) { + // Invalid backupName + msgErr(context, "Invalid backup name! Please check your input. The list index number is also valid.", false); + return FAILED; + } + + // Detect backup type + + // Update pending task + //pendingOperation = AbstractConfirmableOperation.createRestoreOperation(context, backupName); +// File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName)); + // TODO: improve this + ConfiguredBackupMethod method = backupFileName.endsWith(".zip") ? + new ConfiguredPrimitiveBackupMethod( + backupFileName, getLevelPath(server), getBackupSaveDirectory(server).getAbsolutePath() + ) : new ConfiguredIncrementalBackupMethod( + backupFileName, getLevelPath(server), + getBackupSaveDirectory(server).getAbsolutePath(), + getIncrementalBackupBaseDirectory(server).getAbsolutePath() + ); + // String backupSavePath, String levelPath, String backupFileName +// getBackupSaveDirectory(server).getAbsolutePath(), getLevelPath(server), backupFileName + pendingOperation = new RestoreOperation(context, method); + + msgWarn(context, String.format("RESET WARNING: You will LOSE YOUR CURRENT WORLD PERMANENTLY! The worlds will be replaced with backup %s . Use /kb confirm to start or /kb cancel to abort.", backupFileName), true); + return SUCCESS; + } catch (IOException e) { + msgErr(context, String.format("An I/O exception occurred while making backup: %s", e)); + } + return FAILED; + } + + private static int doBackup(CommandContext context, String customBackupName, boolean incremental) { + try { + // Real backup name (compatible with legacy backup): date_name, such as 2020-04-23_21-03-00_test + //KBMain.backup("name") +// String backupName = BackupNameTimeFormatter.getTimeString() + "_" + customBackupName; + + // Validate file name + final char[] ILLEGAL_CHARACTERS = {'/', '\n', '\r', '\t', '\0', '\f', '`', '?', '*', '\\', '<', '>', '|', '\"', ':'}; + for (char c : ILLEGAL_CHARACTERS) { + if (customBackupName.contains(String.valueOf(c))) { + msgErr(context, String.format("Name cannot contain special character \"%c\".", c)); + return FAILED; + } + } + + PrintUtil.info("Start backup..."); + + // configure backup method + MinecraftServer server = context.getSource().getMinecraftServer(); + ConfiguredBackupMethod method = !incremental ? new ConfiguredPrimitiveBackupMethod( + PrimitiveBackupFileNameEncoder.INSTANCE.encode(customBackupName, LocalDateTime.now()), + getLevelPath(server), + getBackupSaveDirectory(server).getCanonicalPath() + ) : new ConfiguredIncrementalBackupMethod( + IncrementalBackupFileNameEncoder.INSTANCE.encode(customBackupName, LocalDateTime.now()), + getLevelPath(server), + getBackupSaveDirectory(server).getCanonicalPath(), + getIncrementalBackupBaseDirectory(server).getCanonicalPath() + ); + + // dispatch to operation worker + BackupOperation operation = new BackupOperation(context, method); + if (operation.invoke()) { + return SUCCESS; + } else if (operation.isBlocked()) { + msgWarn(context, "Another task is running, cannot issue new backup at once."); + return FAILED; + } + } catch (IOException e) { + msgErr(context, String.format("An I/O exception occurred while making backup: %s", e)); + } + return FAILED; + } + + /** + * Restore with context parameter backupName. + * + * @param context the context. + * @return stat code. + */ + public static int confirm(CommandContext context) { + if (pendingOperation == null) { + msgWarn(context, "Nothing to confirm."); + return FAILED; + } + + Invokable operation = pendingOperation; + pendingOperation = null; + + boolean returnValue = operation.invoke(); + + // By the way, update suggestion list. + BackupNameSuggestionProvider.updateCandidateList(); + + return returnValue ? SUCCESS : FAILED; // block compiler's complain. + } + + /** + * Cancel the execution to be confirmed. + * + * @param context the context. + * @return stat code. + */ + public static int cancel(CommandContext context) { + if (pendingOperation != null) { + PrintUtil.msgInfo(context, String.format("The %s has been cancelled.", pendingOperation.toString()), true); + pendingOperation = null; + return SUCCESS; + } else { + msgErr(context, "Nothing to cancel."); + return FAILED; + } + } + + /** + * Show the most recent backup. + * If there is no available backup, print specific info. + * + * @param context the context. + * @return stat code. + */ + public static int prev(CommandContext context) { + // FIXME: This breaks after adding incremental backup + try { + // List all backups + updateBackupList(); +// MinecraftServer server = context.getSource().getMinecraftServer(); +// List files = Arrays.asList(Objects.requireNonNull(getBackupSaveDirectory(server).listFiles())); +// files.removeIf(f -> !f.getName().startsWith(BackupFilesystemUtil.getBackupFileNamePrefix())); +// files.sort((x, y) -> (int) (BackupFilesystemUtil.getBackupTimeFromBackupFileName(y.getName()) - BackupFilesystemUtil.getBackupTimeFromBackupFileName(x.getName()))); +// File prevBackupFile = files.get(0); +// String backupFileName = prevBackupFile.getName(); +// int i; +// synchronized (backupList) { +// i = backupList.indexOf(backupFileName); +// if (i == -1) { +// backupList.add(backupFileName); +// i = backupList.size(); +// } else { +// ++i; +// } +// } + synchronized (backupList) { + if (!backupList.isEmpty()) { + BackupInfo info = backupList.get(0); + msgInfo(context, "The most recent backup:"); + printBackupInfo(context, info, 0); + } else { + msgInfo(context, "There is no backup available."); + } + } + } catch (SecurityException ignored) { + msgErr(context, "Failed to read file."); + return FAILED; + } + return SUCCESS; + } + +// private static String getPrimitiveBackupInformationString(String backupFileName, long backupFileSizeBytes) { +// return String.format( +// "(ZIP) %s , size: %s", +// PrimitiveBackupFileNameEncoder.INSTANCE.decode(backupFileName), +// getFriendlyFileSizeString(backupFileSizeBytes) +// ); +// } + +// private static String getIncrementalBackupInformationString(File backupFile) { +// try { +// SavedIncrementalBackup info = IncBackupInfoSerializer.fromFile(backupFile); +// return "(Incremental) " + info.getBackupName() +// + ", " + DateUtil.getString(info.getBackupTime()) +// + ((info.getTotalSizeBytes() > 0) ? +// (" size: " + BackupFilesystemUtil.getFriendlyFileSizeString(info.getTotalSizeBytes())) : ""); +// } catch (IOException e) { +// e.printStackTrace(); +// return "(Incremental) " + backupFile.getName(); +// } +// } + +// /** +// * Select the backup method we use. +// * @param context the context. +// * @return stat code. +// */ +// public static int setMethod(CommandContext context) { +// String desiredMethodName = StringArgumentType.getString(context, "backupMethod"); +// List backupMethods = Arrays.asList(BackupType.PRIMITIVE_ZIP_BACKUP, BackupType.OBJECT_TREE_BACKUP); +// for (BackupType method : backupMethods) { +// if(method.getName().equals(desiredMethodName)) { +// // Incremental backup +//// activatedBackupMethod = +// msgInfo(context, String.format("Backup method is set to: %s", desiredMethodName)); +// return SUCCESS; +// } +// } +// +// return SUCCESS; +// } + + + private static String parseBackupFileName(CommandContext context, String userInput) { + try { + String backupName = StringArgumentType.getString(context, "backupName"); + + if (backupName.matches("[0-9]*")) { + // If numeric input + int index = Integer.parseInt(backupName) - 1; + synchronized (backupList) { + return backupList.get(index).getBackupFileName(); // Replace input number with real backup file name. + } + } + } catch (NumberFormatException | IndexOutOfBoundsException ignored) { + } + return userInput; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/ui/PrimitiveBackupInfo.java b/src/main/java/com/keuin/kbackupfabric/ui/PrimitiveBackupInfo.java new file mode 100644 index 0000000..8cca8d2 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/ui/PrimitiveBackupInfo.java @@ -0,0 +1,78 @@ +package com.keuin.kbackupfabric.ui; + +import com.keuin.kbackupfabric.backup.name.BackupFileNameEncoder; +import com.keuin.kbackupfabric.backup.name.PrimitiveBackupFileNameEncoder; +import com.keuin.kbackupfabric.operation.backup.method.ConfiguredBackupMethod; +import com.keuin.kbackupfabric.util.FilesystemUtil; +import net.minecraft.server.MinecraftServer; + +import java.io.File; +import java.time.LocalDateTime; + +/** + * Used in UI part. + */ +public class PrimitiveBackupInfo implements BackupInfo { + private final String name; + private final LocalDateTime creationTime; + private final long sizeBytes; + private final String fileName; + + @Deprecated + private PrimitiveBackupInfo(String name, LocalDateTime creationTime, long sizeBytes) { + this.name = name; + this.creationTime = creationTime; + this.sizeBytes = sizeBytes; + this.fileName = PrimitiveBackupFileNameEncoder.INSTANCE.encode(name, creationTime); + } + + private PrimitiveBackupInfo(String fileName, long sizeBytes) { + this.fileName = fileName; + BackupFileNameEncoder.BackupBasicInformation info = PrimitiveBackupFileNameEncoder.INSTANCE.decode(fileName); + if (info == null) + throw new IllegalArgumentException("Invalid file name."); + this.name = info.customName; + this.creationTime = info.time; + this.sizeBytes = sizeBytes; + } + + public static PrimitiveBackupInfo fromFile(File zipFile) { + // TODO: fix this, use metadata file instead +// fileName = zipFile.getName(); +// BackupFileNameEncoder.BackupBasicInformation info = PrimitiveBackupFileNameEncoder.INSTANCE.decode(fileName); +// if (info == null) +// throw new IllegalArgumentException("Invalid file name."); +// return new PrimitiveBackupInfo(info.customName, info.time, FilesystemUtil.getFileSizeBytes(zipFile)); + return new PrimitiveBackupInfo(zipFile.getName(), FilesystemUtil.getFileSizeBytes(zipFile)); + } + + @Override + public String getName() { + return name; + } + + @Override + public LocalDateTime getCreationTime() { + return creationTime; + } + + @Override + public long getSizeBytes() { + return sizeBytes; + } + + @Override + public String getType() { + return "ZIP"; + } + + @Override + public ConfiguredBackupMethod createConfiguredBackupMethod(MinecraftServer server) { + throw new RuntimeException("not implemented"); + } + + @Override + public String getBackupFileName() { + return null; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/DateUtil.java b/src/main/java/com/keuin/kbackupfabric/util/DateUtil.java index b101cfd..7111347 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/DateUtil.java +++ b/src/main/java/com/keuin/kbackupfabric/util/DateUtil.java @@ -1,13 +1,18 @@ package com.keuin.kbackupfabric.util; +import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.Date; public class DateUtil { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); + private static final SimpleDateFormat outFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter prettyFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); public static String getString(LocalDateTime localDateTime) { return localDateTime.format(formatter); @@ -28,4 +33,12 @@ public class DateUtil { public static LocalDateTime toLocalDateTime(String timeString) { return LocalDateTime.parse(timeString, formatter); } + + public static String fromEpochMillis(long epochMillis) { + return outFormatter.format(new Date(epochMillis)); + } + + public static String getPrettyString(LocalDateTime localDateTime) { + return prettyFormatter.format(localDateTime); + } } diff --git a/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java b/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java index f245cff..bccd403 100644 --- a/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java +++ b/src/main/java/com/keuin/kbackupfabric/util/FilesystemUtil.java @@ -15,19 +15,22 @@ public class FilesystemUtil { */ public static long getFileSizeBytes(String parentDirectory, String fileName) { long fileSize = -1; - try{ + try { File backupZipFile = new File(parentDirectory, fileName); fileSize = backupZipFile.length(); - } catch (SecurityException ignored){ + } catch (SecurityException ignored) { } return fileSize; } public static long getFileSizeBytes(String filePath) { + return getFileSizeBytes(new File(filePath)); + } + + public static long getFileSizeBytes(File file) { long fileSize = -1; try { - File backupZipFile = new File(filePath); - fileSize = backupZipFile.length(); + fileSize = file.length(); } catch (SecurityException ignored) { } return fileSize; -- cgit v1.2.3