From d9c2af035ac2edb68bc8b6a1f94b7c772c3509c8 Mon Sep 17 00:00:00 2001 From: Keuin Date: Sun, 2 Aug 2020 18:31:50 +0800 Subject: Refactor & object tree impl. stage 1 --- .../util/backup/BackupFilesystemUtil.java | 82 ++++++++++++++++++++++ .../util/backup/BackupNameSuggestionProvider.java | 77 ++++++++++++++++++++ .../util/backup/BackupNameTimeFormatter.java | 32 +++++++++ .../kbackupfabric/util/backup/BackupType.java | 21 ++++++ .../util/backup/builder/BackupFileNameBuilder.java | 25 +++++++ .../builder/ObjectTreeBackupFileNameBuilder.java | 20 ++++++ .../builder/PrimitiveZipBackupFileNameBuilder.java | 22 ++++++ .../backup/formatter/BackupFileNameFormatter.java | 32 +++++++++ .../ObjectTreeBackupFileNameFormatter.java | 47 +++++++++++++ .../PrimitiveZipBackupFileNameFormatter.java | 46 ++++++++++++ 10 files changed, 404 insertions(+) create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameSuggestionProvider.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameTimeFormatter.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/builder/ObjectTreeBackupFileNameBuilder.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/builder/PrimitiveZipBackupFileNameBuilder.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java create mode 100644 src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java (limited to 'src/main/java/com/keuin/kbackupfabric/util/backup') diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java new file mode 100644 index 0000000..54c2f58 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupFilesystemUtil.java @@ -0,0 +1,82 @@ +package com.keuin.kbackupfabric.util.backup; + +import com.keuin.kbackupfabric.util.ReflectionUtils; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import net.minecraft.world.World; + +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Functions deal with file name, directory name about Minecraft saves. + */ +public final class BackupFilesystemUtil { + + private static final String backupSaveDirectoryName = "backups"; + private static final String backupFileNamePrefix = "kbackup-"; + + public static String getBackupFileNamePrefix() { + return backupFileNamePrefix; + } + + @Deprecated + public static String getBackupFileName(String backupName) { + return backupFileNamePrefix + backupName + ".zip"; + } + + @Deprecated + public static String getBackupName(String backupFileName) { + try { + if (backupFileName.matches(backupFileNamePrefix + ".+\\.zip")) + return backupFileName.substring(backupFileNamePrefix.length(), backupFileName.length() - 4); + } catch (IndexOutOfBoundsException ignored) { + } + return backupFileName; + } + + public static boolean isBackupNameValid(String backupName, MinecraftServer server) { + File backupFile = new File(getBackupSaveDirectory(server), getBackupFileName(backupName)); + return backupFile.isFile(); + } + + public static File getBackupSaveDirectory(MinecraftServer server) { + return new File(server.getRunDirectory(), backupSaveDirectoryName); + } + + public static String getLevelPath(MinecraftServer server) { + return (new File(server.getRunDirectory(), server.getLevelName())).getAbsolutePath(); + } + + public static String getWorldDirectoryName(World world) throws NoSuchFieldException, IllegalAccessException { + File saveDir; + ThreadedAnvilChunkStorage threadedAnvilChunkStorage = (ThreadedAnvilChunkStorage) ReflectionUtils.getPrivateField(world.getChunkManager(), "threadedAnvilChunkStorage"); + saveDir = (File) ReflectionUtils.getPrivateField(threadedAnvilChunkStorage, "saveDir"); + return saveDir.getName(); + } + + @Deprecated + public static long getBackupTimeFromBackupFileName(String backupFileName) { + Matcher matcher = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}").matcher(backupFileName); + if (matcher.find()) { + String timeString = matcher.group(0); + long timeStamp = BackupNameTimeFormatter.timeStringToEpochSeconds(timeString); + System.out.println(backupFileName + " -> " + timeStamp); + return timeStamp; + } else { + System.err.println("Failed to extract time from " + backupFileName); + } + return -1; + } + + public static String humanFileSize(long size) { + double fileSize = size * 1.0 / 1024 / 1024; // Default unit is MB + if (fileSize > 1000) + //msgInfo(context, String.format("File size: %.2fGB", fileSize / 1024)); + return String.format("%.2fGB", fileSize / 1024); + else + //msgInfo(context, String.format("File size: %.2fMB", fileSize)); + return String.format("%.2fMB", fileSize); + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameSuggestionProvider.java b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameSuggestionProvider.java new file mode 100644 index 0000000..4639e99 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameSuggestionProvider.java @@ -0,0 +1,77 @@ +package com.keuin.kbackupfabric.util.backup; + +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.server.command.ServerCommandSource; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; + +public class BackupNameSuggestionProvider { + + private static final List candidateCacheList = new ArrayList<>(); + private static final Object syncSetDirectory = new Object(); + private static final Object syncCache = new Object(); + private static final long CACHE_TTL = 8000; + private static String backupSaveDirectory; + private static long cacheUpdateTime = 0; + + public static void setBackupSaveDirectory(String backupSaveDirectory) { + synchronized (syncSetDirectory) { + BackupNameSuggestionProvider.backupSaveDirectory = backupSaveDirectory; + } + // Immediately perform an update + updateCandidateList(); + } + + public static void updateCandidateList() { + synchronized (syncCache) { + try { + File file = new File(backupSaveDirectory); + candidateCacheList.clear(); + File[] files = file.listFiles(); + if (files == null) + return; + for (File f : files) + candidateCacheList.add(BackupFilesystemUtil.getBackupName(f.getName())); + cacheUpdateTime = System.currentTimeMillis(); + } catch (NullPointerException ignored) { + } + } + } + +// private static void updateCandidateList(Collection stringCollection) { +// candidateList.clear(); +// candidateList.addAll(stringCollection); +// } + + public static SuggestionProvider getProvider() { + return (context, builder) -> getCompletableFuture(builder); + } + + private static CompletableFuture getCompletableFuture(SuggestionsBuilder builder) { + if (isCacheExpired()) + updateCandidateList(); + String remaining = builder.getRemaining().toLowerCase(Locale.ROOT); + synchronized (syncCache) { + if (candidateCacheList.isEmpty()) { // If the list is empty then return no suggestions + return Suggestions.empty(); // No suggestions + } + + for (String string : candidateCacheList) { // Iterate through the supplied list + if (string.toLowerCase(Locale.ROOT).startsWith(remaining)) { + builder.suggest(string); // Add every single entry to suggestions list. + } + } + } + return builder.buildFuture(); // Create the CompletableFuture containing all the suggestions + } + + private static boolean isCacheExpired() { + return System.currentTimeMillis() - cacheUpdateTime > CACHE_TTL || cacheUpdateTime == 0; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameTimeFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameTimeFormatter.java new file mode 100644 index 0000000..3811ae8 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupNameTimeFormatter.java @@ -0,0 +1,32 @@ +package com.keuin.kbackupfabric.util.backup; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; + +public class BackupNameTimeFormatter { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); + + @Deprecated + public static String getTimeString() { + return LocalDateTime.now().format(formatter); + } + + public static String localDateTimeToString(LocalDateTime localDateTime) { + return localDateTime.format(formatter); + } + + @Deprecated + public static long timeStringToEpochSeconds(String timeString) { + ZoneId systemZone = ZoneId.systemDefault(); // my timezone + LocalDateTime localDateTime = LocalDateTime.parse(timeString, formatter); + ZoneOffset currentOffsetForMyZone = systemZone.getRules().getOffset(localDateTime); + return localDateTime.toEpochSecond(currentOffsetForMyZone); + } + + public static LocalDateTime timeStringToLocalDateTime(String timeString) { + return LocalDateTime.parse(timeString,formatter); + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java new file mode 100644 index 0000000..95a32ae --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/BackupType.java @@ -0,0 +1,21 @@ +package com.keuin.kbackupfabric.util.backup; + +/** + * Representing the backup type. + * Should only be used in BackupFileNameBuilder and BackupFileNameFormatter + */ +public enum BackupType { + + PRIMITIVE_ZIP_BACKUP("Primitive Zip Backup"), + OBJECT_TREE_BACKUP("Object Tree Backup"); + + private final String name; + BackupType(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java new file mode 100644 index 0000000..16c598b --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/BackupFileNameBuilder.java @@ -0,0 +1,25 @@ +package com.keuin.kbackupfabric.util.backup.builder; + +import com.sun.istack.internal.NotNull; + +import java.time.LocalDateTime; + +public interface BackupFileNameBuilder { + + static BackupFileNameBuilder primitiveZipBackup() { + return PrimitiveZipBackupFileNameBuilder.getInstance(); + } + + static BackupFileNameBuilder objectTreeBackup() { + return ObjectTreeBackupFileNameBuilder.getInstance(); + } + + /** + * Build a backup file name based on given information. + * @param time when the backup was created. + * @param backupName the custom name of this backup. Note that this should be a valid file name in current file system. + * @return the backup file name string. + */ + String build(@NotNull LocalDateTime time, @NotNull String backupName); + +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/builder/ObjectTreeBackupFileNameBuilder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/ObjectTreeBackupFileNameBuilder.java new file mode 100644 index 0000000..3c15741 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/ObjectTreeBackupFileNameBuilder.java @@ -0,0 +1,20 @@ +package com.keuin.kbackupfabric.util.backup.builder; + +import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil; +import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter; + +import java.time.LocalDateTime; + +public class ObjectTreeBackupFileNameBuilder implements BackupFileNameBuilder { + private static final ObjectTreeBackupFileNameBuilder instance = new ObjectTreeBackupFileNameBuilder(); + + public static ObjectTreeBackupFileNameBuilder getInstance() { + return instance; + } + + @Override + public String build(LocalDateTime time, String backupName) { + String timeString = BackupNameTimeFormatter.localDateTimeToString(time); + return String.format("%s%s_%s%s", BackupFilesystemUtil.getBackupFileNamePrefix(),timeString,backupName,".json"); + } +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/builder/PrimitiveZipBackupFileNameBuilder.java b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/PrimitiveZipBackupFileNameBuilder.java new file mode 100644 index 0000000..f910c37 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/builder/PrimitiveZipBackupFileNameBuilder.java @@ -0,0 +1,22 @@ +package com.keuin.kbackupfabric.util.backup.builder; + +import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil; +import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter; + +import java.time.LocalDateTime; + +public class PrimitiveZipBackupFileNameBuilder implements BackupFileNameBuilder { + + private static final PrimitiveZipBackupFileNameBuilder instance = new PrimitiveZipBackupFileNameBuilder(); + + public static PrimitiveZipBackupFileNameBuilder getInstance() { + return instance; + } + + @Override + public String build(LocalDateTime time, String backupName) { + String timeString = BackupNameTimeFormatter.localDateTimeToString(time); + return String.format("%s%s_%s%s", BackupFilesystemUtil.getBackupFileNamePrefix(),timeString,backupName,".zip"); + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java new file mode 100644 index 0000000..eae3639 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/BackupFileNameFormatter.java @@ -0,0 +1,32 @@ +package com.keuin.kbackupfabric.util.backup.formatter; + +import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter; +import com.sun.istack.internal.NotNull; + +import java.time.LocalDateTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public interface BackupFileNameFormatter { + + BackupFileName format(@NotNull String fileName); + + class BackupFileName { + public final LocalDateTime time; + public final String name; + + public BackupFileName(LocalDateTime time, String name) { + this.time = time; + this.name = name; + } + } + + static BackupFileNameFormatter objectTreeBackup() { + return ObjectTreeBackupFileNameFormatter.getInstance(); + } + + static BackupFileNameFormatter primitiveZipBackup() { + return PrimitiveZipBackupFileNameFormatter.getInstance(); + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java new file mode 100644 index 0000000..e5503a9 --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/ObjectTreeBackupFileNameFormatter.java @@ -0,0 +1,47 @@ +package com.keuin.kbackupfabric.util.backup.formatter; + +import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil; +import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter; +import com.sun.istack.internal.NotNull; +import com.sun.istack.internal.Nullable; +import org.spongepowered.asm.mixin.Overwrite; + +import java.time.LocalDateTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ObjectTreeBackupFileNameFormatter implements BackupFileNameFormatter { + + private static final ObjectTreeBackupFileNameFormatter instance = new ObjectTreeBackupFileNameFormatter(); + + public static ObjectTreeBackupFileNameFormatter getInstance() { + return instance; + } + + @Override + public BackupFileNameFormatter.BackupFileName format(@NotNull String fileName) { + LocalDateTime time = getTime(fileName); + String name = getBackupName(fileName); + return new BackupFileNameFormatter.BackupFileName(time,name); + } + + @Nullable + private LocalDateTime getTime(String fileName) { + Matcher matcher = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}").matcher(fileName); + if (matcher.find()) { + String timeString = matcher.group(0); + return BackupNameTimeFormatter.timeStringToLocalDateTime(timeString); + } + return null; + } + + private String getBackupName(String backupFileName) { + try { + if (backupFileName.matches(BackupFilesystemUtil.getBackupFileNamePrefix() + ".+\\.json")) + return backupFileName.substring(BackupFilesystemUtil.getBackupFileNamePrefix().length(), backupFileName.length() - 4); + } catch (IndexOutOfBoundsException ignored) { + } + return backupFileName; + } + +} diff --git a/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java new file mode 100644 index 0000000..40450eb --- /dev/null +++ b/src/main/java/com/keuin/kbackupfabric/util/backup/formatter/PrimitiveZipBackupFileNameFormatter.java @@ -0,0 +1,46 @@ +package com.keuin.kbackupfabric.util.backup.formatter; + +import com.keuin.kbackupfabric.util.backup.BackupFilesystemUtil; +import com.keuin.kbackupfabric.util.backup.BackupNameTimeFormatter; +import com.sun.istack.internal.NotNull; +import com.sun.istack.internal.Nullable; + +import java.time.LocalDateTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PrimitiveZipBackupFileNameFormatter implements BackupFileNameFormatter { + + private static final PrimitiveZipBackupFileNameFormatter instance = new PrimitiveZipBackupFileNameFormatter(); + + public static PrimitiveZipBackupFileNameFormatter getInstance() { + return instance; + } + + @Override + public BackupFileNameFormatter.BackupFileName format(@NotNull String fileName) { + LocalDateTime time = getTime(fileName); + String name = getBackupName(fileName); + return new BackupFileNameFormatter.BackupFileName(time,name); + } + + @Nullable + private LocalDateTime getTime(String fileName) { + Matcher matcher = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}").matcher(fileName); + if (matcher.find()) { + String timeString = matcher.group(0); + return BackupNameTimeFormatter.timeStringToLocalDateTime(timeString); + } + return null; + } + + private String getBackupName(String backupFileName) { + try { + if (backupFileName.matches(BackupFilesystemUtil.getBackupFileNamePrefix() + ".+\\.zip")) + return backupFileName.substring(BackupFilesystemUtil.getBackupFileNamePrefix().length(), backupFileName.length() - 4); + } catch (IndexOutOfBoundsException ignored) { + } + return backupFileName; + } + +} -- cgit v1.2.3