From b96622f1cd8bbffcb0e473c92f93d73bc58d74d3 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 17 Dec 2022 16:22:21 -0600 Subject: [PATCH] refactor the AbstractSaveStructure classes --- .../file/structure/AbstractSaveStructure.java | 36 ++ .../structure/ClientOnlySaveStructure.java | 379 +++++++++++------- .../file/structure/LocalSaveStructure.java | 64 ++- .../core/file/structure/SaveStructure.java | 21 - .../seibel/lod/core/util/FileScanUtil.java | 4 +- .../lod/core/world/DhClientServerWorld.java | 2 +- .../seibel/lod/core/world/DhClientWorld.java | 2 +- .../seibel/lod/core/world/DhServerWorld.java | 2 +- 8 files changed, 309 insertions(+), 201 deletions(-) create mode 100644 core/src/main/java/com/seibel/lod/core/file/structure/AbstractSaveStructure.java delete mode 100644 core/src/main/java/com/seibel/lod/core/file/structure/SaveStructure.java diff --git a/core/src/main/java/com/seibel/lod/core/file/structure/AbstractSaveStructure.java b/core/src/main/java/com/seibel/lod/core/file/structure/AbstractSaveStructure.java new file mode 100644 index 000000000..8818db5b2 --- /dev/null +++ b/core/src/main/java/com/seibel/lod/core/file/structure/AbstractSaveStructure.java @@ -0,0 +1,36 @@ +package com.seibel.lod.core.file.structure; + +import com.seibel.lod.core.logging.DhLoggerBuilder; +import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; +import org.apache.logging.log4j.Logger; + +import java.io.File; + +/** + * Abstract class for determining where LOD data should be saved to. + * + * @version 2022-12-17 + */ +public abstract class AbstractSaveStructure implements AutoCloseable +{ + public static final String RENDER_CACHE_FOLDER = "cache"; + public static final String DATA_FOLDER = "data"; + + protected static final Logger LOGGER = DhLoggerBuilder.getLogger(); + + /** + * Attempts to return the folder that contains LOD data for the given {@link ILevelWrapper}. + * If no appropriate folder exists, one will be created.

+ * + * This will always return a folder, however that folder may not be the best match + * if multiverse support is enabled. + * */ + public abstract File tryGetOrCreateLevelFolder(ILevelWrapper wrapper); + + /** Will return null if no parent folder exists for the given {@link ILevelWrapper}.*/ + public abstract File getRenderCacheFolder(ILevelWrapper world); + /** Will return null if no parent folder exists for the given {@link ILevelWrapper}.*/ + public abstract File getDataFolder(ILevelWrapper world); + +} + diff --git a/core/src/main/java/com/seibel/lod/core/file/structure/ClientOnlySaveStructure.java b/core/src/main/java/com/seibel/lod/core/file/structure/ClientOnlySaveStructure.java index 517e5e002..1b1c93664 100644 --- a/core/src/main/java/com/seibel/lod/core/file/structure/ClientOnlySaveStructure.java +++ b/core/src/main/java/com/seibel/lod/core/file/structure/ClientOnlySaveStructure.java @@ -12,159 +12,232 @@ import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; import javax.annotation.Nullable; import java.io.File; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Optional; +import java.util.*; import java.util.stream.Stream; -public class ClientOnlySaveStructure extends SaveStructure { - final File folder; - private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]"; - private static String getServerFolderName() - { - // parse the current server's IP - ParsedIp parsedIp = new ParsedIp(MC_CLIENT.getCurrentServerIp()); - String serverIpCleaned = parsedIp.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); - String serverPortCleaned = parsedIp.port != null ? parsedIp.port.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "") : ""; - - // determine the format of the folder name - EServerFolderNameMode folderNameMode = Config.Client.Multiplayer.serverFolderNameMode.get(); - if (folderNameMode == EServerFolderNameMode.AUTO) - { - if (parsedIp.isLan()) - { - // LAN - folderNameMode = EServerFolderNameMode.NAME_IP; - } - else - { - // normal multiplayer - folderNameMode = EServerFolderNameMode.NAME_IP_PORT; - } - } - String serverName = MC_CLIENT.getCurrentServerName().replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); - String serverMcVersion = MC_CLIENT.getCurrentServerVersion().replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); - // generate the folder name - String folderName = ""; - switch (folderNameMode) - { - // default and auto shouldn't be used - // and are just here to make the compiler happy - default: - case NAME_ONLY: - folderName = serverName; - break; - - case NAME_IP: - folderName = serverName + ", IP " + serverIpCleaned; - break; - case NAME_IP_PORT: - folderName = serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : ""); - break; - case NAME_IP_PORT_MC_VERSION: - folderName = serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : "") + ", GameVersion " + serverMcVersion; - break; - } - // PercentEscaper makes the characters all part of the standard alphameric character set - // This fixes some issues when the server is named something in other languages - return new PercentEscaper("", true).escape(folderName); - } - - SubDimensionLevelMatcher fileMatcher = null; - final HashMap levelToFileMap = new HashMap<>(); - - // Fit for Client_Only environment - public ClientOnlySaveStructure() { - folder = new File(MC_CLIENT.getGameDirectory().getPath() + - File.separatorChar + "Distant_Horizons_server_data" + File.separatorChar + getServerFolderName()); - if (!folder.exists()) folder.mkdirs(); //TODO: Deal with errors - } - - @Override - public File tryGetLevelFolder(ILevelWrapper level) { - return levelToFileMap.computeIfAbsent(level, (l) -> { - if (Config.Client.Multiplayer.multiDimensionRequiredSimilarity.get() == 0) { - if (fileMatcher != null) { - fileMatcher.close(); - fileMatcher = null; - } - return getLevelFolderWithoutSimilarityMatching(l); - } - if (fileMatcher == null || !fileMatcher.isFindingLevel(l)) { - LOGGER.info("Loading level for world " + l.getDimensionType().getDimensionName()); - fileMatcher = new SubDimensionLevelMatcher(l, folder, - (File[]) getMatchingLevelFolders(l).toArray()); - } - File levelFile = fileMatcher.tryGetLevel(); - if (levelFile != null) { - fileMatcher.close(); - fileMatcher = null; - } - return levelFile; - }); - } - - private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level) - { - Stream folders = getMatchingLevelFolders(level); - Optional first = folders.findFirst(); - if (first.isPresent()) - { - LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(first.get().getName(), 8) + "...]"); - return first.get(); - } else { // if no valid sub dimension was found, create a new one - LOGGER.info("Default Sub Dimension not found. Creating: [" + level.getDimensionType().getDimensionName() + "]"); - return new File(folder, level.getDimensionType().getDimensionName()); - } - } - - public Stream getMatchingLevelFolders(@Nullable ILevelWrapper level) { - File[] folders = folder.listFiles(); - if (folders==null) return Stream.empty(); - return Arrays.stream(folders).filter( - (f) -> { - if (!isValidLevelFolder(f)) return false; - return level==null || f.getName().equalsIgnoreCase(level.getDimensionType().getDimensionName()); - } - ).sorted(); - } - - /** Returns true if the given folder holds valid Lod Dimension data */ - private static boolean isValidLevelFolder(File potentialFolder) - { - if (!potentialFolder.isDirectory()) - // it needs to be a folder - return false; - - File[] files = potentialFolder.listFiles((f) -> f.isDirectory() && - (f.getName().equalsIgnoreCase(RENDER_CACHE_FOLDER) || f.getName().equalsIgnoreCase(DATA_FOLDER))); - // it needs to have folders with specified names in it - return files != null && files.length != 0; - } - - - @Override - public File getRenderCacheFolder(ILevelWrapper level) { - File levelFolder = levelToFileMap.get(level); - if (levelFolder == null) return null; - return new File(levelFolder, RENDER_CACHE_FOLDER); - } - - @Override - public File getDataFolder(ILevelWrapper level) { - File levelFolder = levelToFileMap.get(level); - if (levelFolder == null) return null; - return new File(levelFolder, DATA_FOLDER); - } - - @Override - public void close() { - fileMatcher.close(); - } - - @Override - public String toString() { - return "[ClientOnlySave@"+folder.getName()+"]"; - } +/** + * Designed for the Client_Only environment. + * + * @version 12-17-2022 + */ +public class ClientOnlySaveStructure extends AbstractSaveStructure +{ + final File folder; + private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]"; + + SubDimensionLevelMatcher fileMatcher = null; + final HashMap levelToFileMap = new HashMap<>(); + + + + public ClientOnlySaveStructure() + { + this.folder = new File(MC_CLIENT.getGameDirectory().getPath() + + File.separatorChar + "Distant_Horizons_server_data" + File.separatorChar + getServerFolderName()); + + if (!this.folder.exists()) + { + if (!this.folder.mkdirs()) + { + LOGGER.warn("Unable to create folder [" + this.folder.getPath() + "]"); + //TODO: Deal with errors + } + } + } + + + + //================// + // folder methods // + //================// + + @Override + public File tryGetOrCreateLevelFolder(ILevelWrapper level) + { + return this.levelToFileMap.computeIfAbsent(level, (newLevel) -> + { + if (Config.Client.Multiplayer.multiDimensionRequiredSimilarity.get() == 0) + { + if (this.fileMatcher != null) + { + this.fileMatcher.close(); + this.fileMatcher = null; + } + return this.getLevelFolderWithoutSimilarityMatching(newLevel); + } + + if (this.fileMatcher == null || !this.fileMatcher.isFindingLevel(newLevel)) + { + LOGGER.info("Loading level for world " + newLevel.getDimensionType().getDimensionName()); + this.fileMatcher = new SubDimensionLevelMatcher(newLevel, this.folder, + this.getMatchingLevelFolders(newLevel).toArray(new File[0] /* surprisingly we don't need to create an array of any specific size for this to work */)); + } + + File levelFile = this.fileMatcher.tryGetLevel(); + if (levelFile != null) + { + this.fileMatcher.close(); + this.fileMatcher = null; + } + return levelFile; + }); + } + + private File getLevelFolderWithoutSimilarityMatching(ILevelWrapper level) + { + List folders = this.getMatchingLevelFolders(level); + if (folders.size() > 0 && folders.get(0) == null) + { + LOGGER.info("Default Sub Dimension set to: [" + LodUtil.shortenString(folders.get(0).getName(), 8) + "...]"); + return folders.get(0); + } + else + { + // if no valid sub dimension was found, create a new one + LOGGER.info("Default Sub Dimension not found. Creating: [" + level.getDimensionType().getDimensionName() + "]"); + return new File(this.folder, level.getDimensionType().getDimensionName()); + } + } + + public List getMatchingLevelFolders(@Nullable ILevelWrapper level) + { + File[] folders = this.folder.listFiles(); + if (folders == null) + { + return new ArrayList<>(0); + } + + Stream fileStream = Arrays.stream(folders).filter( + (folder) -> + { + if (!isValidLevelFolder(folder)) + { + return false; + } + else + { + return level == null || folder.getName().equalsIgnoreCase(level.getDimensionType().getDimensionName()); + } + } + ).sorted(); + + return fileStream.toList(); + } + + @Override + public File getRenderCacheFolder(ILevelWrapper level) + { + File levelFolder = this.levelToFileMap.get(level); + if (levelFolder == null) + { + return null; + } + + return new File(levelFolder, RENDER_CACHE_FOLDER); + } + + @Override + public File getDataFolder(ILevelWrapper level) + { + File levelFolder = this.levelToFileMap.get(level); + if (levelFolder == null) + { + return null; + } + + return new File(levelFolder, DATA_FOLDER); + } + + + + //================// + // helper methods // + //================// + + /** Returns true if the given folder holds valid Lod Dimension data */ + private static boolean isValidLevelFolder(File potentialFolder) + { + if (!potentialFolder.isDirectory()) + { + // a valid level folder needs to be a folder + return false; + } + + // filter out any non-DH folders + File[] files = potentialFolder.listFiles((file) -> + file.isDirectory() && + (file.getName().equalsIgnoreCase(RENDER_CACHE_FOLDER) || file.getName().equalsIgnoreCase(DATA_FOLDER))); + + // a valid level folder needs to have DH specific folders in it + return files != null && files.length != 0; + } + + /** Generated from the server the client is currently connected to. */ + private static String getServerFolderName() + { + // parse the current server's IP + ParsedIp parsedIp = new ParsedIp(MC_CLIENT.getCurrentServerIp()); + String serverIpCleaned = parsedIp.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); + String serverPortCleaned = parsedIp.port != null ? parsedIp.port.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "") : ""; + + + // determine the auto folder name format + EServerFolderNameMode folderNameMode = Config.Client.Multiplayer.serverFolderNameMode.get(); + if (folderNameMode == EServerFolderNameMode.AUTO) + { + if (parsedIp.isLan()) + { + // LAN + folderNameMode = EServerFolderNameMode.NAME_IP; + } + else + { + // normal multiplayer + folderNameMode = EServerFolderNameMode.NAME_IP_PORT; + } + // TODO can we determine if a server is a Mojang operated Realm based on the IP? + // If so we should also default to either NAME_IP or just NAME + } + String serverName = MC_CLIENT.getCurrentServerName().replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); + String serverMcVersion = MC_CLIENT.getCurrentServerVersion().replaceAll(INVALID_FILE_CHARACTERS_REGEX, ""); + + + // generate the folder name + String folderName; + switch (folderNameMode) + { + default: + case NAME_ONLY: + folderName = serverName; + break; + + case NAME_IP: + folderName = serverName + ", IP " + serverIpCleaned; + break; + case NAME_IP_PORT: + folderName = serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : ""); + break; + case NAME_IP_PORT_MC_VERSION: + folderName = serverName + ", IP " + serverIpCleaned + (serverPortCleaned.length() != 0 ? ("-" + serverPortCleaned) : "") + ", GameVersion " + serverMcVersion; + break; + } + + // PercentEscaper makes the characters all part of the standard alphameric character set + // This fixes some issues when the server is named something in other languages + return new PercentEscaper("", true).escape(folderName); + } + + + + //==================// + // override methods // + //==================// + + @Override + public void close() { this.fileMatcher.close(); } + + @Override + public String toString() { return "[" + this.getClass().getSimpleName() + "@" + this.folder.getName() + "]"; } + } diff --git a/core/src/main/java/com/seibel/lod/core/file/structure/LocalSaveStructure.java b/core/src/main/java/com/seibel/lod/core/file/structure/LocalSaveStructure.java index f50aae4db..165c5b247 100644 --- a/core/src/main/java/com/seibel/lod/core/file/structure/LocalSaveStructure.java +++ b/core/src/main/java/com/seibel/lod/core/file/structure/LocalSaveStructure.java @@ -5,41 +5,61 @@ import com.seibel.lod.core.wrapperInterfaces.world.IServerLevelWrapper; import java.io.File; -public class LocalSaveStructure extends SaveStructure { +/** + * Designed for Client_Server & Server_Only environments. + * + * @version 2022-12-17 + */ +public class LocalSaveStructure extends AbstractSaveStructure +{ + public static final String SERVER_FOLDER_NAME = "Distant_Horizons"; + private File debugPath = new File(""); - // Fit for Client_Server & Server_Only environment - public LocalSaveStructure() { - } - - @Override - public File tryGetLevelFolder(ILevelWrapper wrapper) { + + + public LocalSaveStructure() { } + + + + //================// + // folder methods // + //================// + + @Override + public File tryGetOrCreateLevelFolder(ILevelWrapper wrapper) + { IServerLevelWrapper serverSide = (IServerLevelWrapper) wrapper; - debugPath = new File(serverSide.getSaveFolder(), "Distant_Horizons"); + this.debugPath = new File(serverSide.getSaveFolder(), "Distant_Horizons"); return new File(serverSide.getSaveFolder(), "Distant_Horizons"); } @Override - public File getRenderCacheFolder(ILevelWrapper level) { + public File getRenderCacheFolder(ILevelWrapper level) + { IServerLevelWrapper serverSide = (IServerLevelWrapper) level; - debugPath = new File(serverSide.getSaveFolder(), "Distant_Horizons"); + this.debugPath = new File(serverSide.getSaveFolder(), "Distant_Horizons"); return new File(new File(serverSide.getSaveFolder(), "Distant_Horizons"), RENDER_CACHE_FOLDER); } @Override - public File getDataFolder(ILevelWrapper level) { + public File getDataFolder(ILevelWrapper level) + { IServerLevelWrapper serverSide = (IServerLevelWrapper) level; - debugPath = new File(serverSide.getSaveFolder(), "Distant_Horizons"); - return new File(new File(serverSide.getSaveFolder(), "Distant_Horizons"), DATA_FOLDER); + this.debugPath = new File(serverSide.getSaveFolder(), SERVER_FOLDER_NAME); + return new File(new File(serverSide.getSaveFolder(), SERVER_FOLDER_NAME), DATA_FOLDER); } - + + + + //==================// + // override methods // + //==================// + + @Override + public void close() throws Exception { } + @Override - public void close() throws Exception { - - } - - @Override - public String toString() { - return "[LocalSave at ["+debugPath+"] ]"; - } + public String toString() { return "[" + this.getClass().getSimpleName() + "@" + this.debugPath + "]"; } + } diff --git a/core/src/main/java/com/seibel/lod/core/file/structure/SaveStructure.java b/core/src/main/java/com/seibel/lod/core/file/structure/SaveStructure.java deleted file mode 100644 index b9aa57ae1..000000000 --- a/core/src/main/java/com/seibel/lod/core/file/structure/SaveStructure.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seibel.lod.core.file.structure; - -import com.seibel.lod.core.logging.DhLoggerBuilder; -import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; -import org.apache.logging.log4j.Logger; - -import java.io.File; - -public abstract class SaveStructure implements AutoCloseable { - - public static final String RENDER_CACHE_FOLDER = "cache"; - public static final String DATA_FOLDER = "data"; - - protected static final Logger LOGGER = DhLoggerBuilder.getLogger(); - - public abstract File tryGetLevelFolder(ILevelWrapper wrapper); - - public abstract File getRenderCacheFolder(ILevelWrapper world); - public abstract File getDataFolder(ILevelWrapper world); -} - diff --git a/core/src/main/java/com/seibel/lod/core/util/FileScanUtil.java b/core/src/main/java/com/seibel/lod/core/util/FileScanUtil.java index 4abb5836f..4c3ff07ab 100644 --- a/core/src/main/java/com/seibel/lod/core/util/FileScanUtil.java +++ b/core/src/main/java/com/seibel/lod/core/util/FileScanUtil.java @@ -2,7 +2,7 @@ package com.seibel.lod.core.util; import com.seibel.lod.core.file.datafile.IDataSourceProvider; import com.seibel.lod.core.file.renderfile.IRenderSourceProvider; -import com.seibel.lod.core.file.structure.SaveStructure; +import com.seibel.lod.core.file.structure.AbstractSaveStructure; import com.seibel.lod.core.logging.DhLoggerBuilder; import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper; import org.apache.logging.log4j.Logger; @@ -18,7 +18,7 @@ public class FileScanUtil { private static final Logger LOGGER = DhLoggerBuilder.getLogger(); public static final int MAX_SCAN_DEPTH = 5; public static final String LOD_FILE_POSTFIX = ".lod"; - public static void scanFile(SaveStructure save, ILevelWrapper level, + public static void scanFile(AbstractSaveStructure save, ILevelWrapper level, @Nullable IDataSourceProvider dataSource, @Nullable IRenderSourceProvider renderSource) { if (dataSource != null) { diff --git a/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java index 0250fb5c3..2c7c46873 100644 --- a/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/lod/core/world/DhClientServerWorld.java @@ -39,7 +39,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper) { if (wrapper instanceof IServerLevelWrapper) { return levelObjMap.computeIfAbsent(wrapper, (w) -> { - File levelFile = saveStructure.tryGetLevelFolder(w); + File levelFile = saveStructure.tryGetOrCreateLevelFolder(w); LodUtil.assertTrue(levelFile != null); DhClientServerLevel level = new DhClientServerLevel(saveStructure, (IServerLevelWrapper) w); dhLevels.add(level); diff --git a/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java index f0f3d3279..f76b04518 100644 --- a/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/lod/core/world/DhClientWorld.java @@ -35,7 +35,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld if (!(wrapper instanceof IClientLevelWrapper)) return null; return levels.computeIfAbsent((IClientLevelWrapper) wrapper, (w) -> { - File level = saveStructure.tryGetLevelFolder(wrapper); + File level = saveStructure.tryGetOrCreateLevelFolder(wrapper); if (level == null) return null; return new DhClientLevel(saveStructure, w); }); diff --git a/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java index b2381de7a..60eeba014 100644 --- a/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/lod/core/world/DhServerWorld.java @@ -27,7 +27,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld public DhServerLevel getOrLoadLevel(ILevelWrapper wrapper) { if (!(wrapper instanceof IServerLevelWrapper)) return null; return levels.computeIfAbsent((IServerLevelWrapper) wrapper, (w) -> { - File levelFile = saveStructure.tryGetLevelFolder(wrapper); + File levelFile = saveStructure.tryGetOrCreateLevelFolder(wrapper); LodUtil.assertTrue(levelFile != null); return new DhServerLevel(saveStructure, w); });