diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/registry/CliExtensibleRegistry.java b/common/implementation/base/src/main/java/com/dfsek/terra/registry/CliExtensibleRegistry.java new file mode 100644 index 0000000000..032e11a1a9 --- /dev/null +++ b/common/implementation/base/src/main/java/com/dfsek/terra/registry/CliExtensibleRegistry.java @@ -0,0 +1,93 @@ +package com.dfsek.terra.registry; + +import com.google.errorprone.annotations.MustBeClosed; +import org.intellij.lang.annotations.Pattern; +import org.jspecify.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Objects; +import java.util.stream.Stream; + + +/** + * A file-discoverable registry that can be extended by system properties + *
+ * The main use-case for this is to allow for customized locations of file-system-based key entries, + * such as putting config packs into custom locations. + *
+ * Two system properties are used to define the search path: + *
+ * terra.registry.{searchPathName}.searchPath is a list of "parent folder" paths (like the pack directory) that + * will be searched for contained files. + *
+ * terra.registry.{searchPathName}.extraPath is a list of files that will be directly evaluated for membership and + * as such will be directly treated as members of the registry. + *
+ * These system properties are formatted like UNIX-style paths, with each path separated by a colon (':') character. + */ +public interface CliExtensibleRegistry { + /** + * Get a lowercase search path name for this registry that will get plugged into + *
+ * terra.registry.{searchPathName}.searchPath + *
+ * and + *
+ * terra.registry.{searchPathName}.extraPath + */ + @Pattern("^[a-z._]+$") + String getRegistryName(); + + /** + * Check if a path may be a member of this registry heuristically. + * + * @param path Path to check + * + * @return True if the path is a member of this registry. + */ + boolean validatePathIsMember(Path path); + + default @MustBeClosed Stream getMemberPaths(Path baseSearchPath) { + String basePropertyName = "terra.registry." + getRegistryName(); + + Stream searchPath = Stream.concat( + parseValidPaths(System.getProperty(basePropertyName + ".searchPath")), + Stream.of(baseSearchPath) + ); + + Stream extraPath = parseValidPaths(System.getProperty(basePropertyName + ".extraPath")); + + return Stream.concat( + searchPath + .filter(Files::isDirectory) + .flatMap(CliExtensibleRegistry::listDirectory), + extraPath + ) + .filter(this::validatePathIsMember); + } + + private static Stream parseValidPaths(@Nullable String paths) { + return Stream.ofNullable(paths) + .flatMap(p -> Arrays.stream(p.split(":"))) + .filter(v -> !v.isBlank()) + .map(v -> { + try { + return Path.of(v).toAbsolutePath().normalize(); + } catch (Exception e) { + return null; + } + }) + .filter(Objects::nonNull); + } + + private static Stream listDirectory(Path dir) { + try { + return Files.list(dir); + } catch(IOException e) { + return Stream.empty(); + } + } +} diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/ConfigRegistry.java b/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/ConfigRegistry.java index f4b4c9dd1a..831188299b 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/ConfigRegistry.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/ConfigRegistry.java @@ -29,13 +29,14 @@ import com.dfsek.terra.api.config.ConfigPack; import com.dfsek.terra.api.util.reflection.TypeKey; import com.dfsek.terra.config.pack.ConfigPackImpl; +import com.dfsek.terra.registry.CliExtensibleRegistry; import com.dfsek.terra.registry.OpenRegistryImpl; /** * Class to hold config packs */ -public class ConfigRegistry extends OpenRegistryImpl { +public class ConfigRegistry extends OpenRegistryImpl implements CliExtensibleRegistry { public ConfigRegistry() { super(TypeKey.of(ConfigPack.class)); @@ -45,7 +46,7 @@ public synchronized void loadAll(Platform platform) throws IOException, PackLoad Path packsDirectory = platform.getDataFolder().toPath().resolve("packs"); Files.createDirectories(packsDirectory); List failedLoads = new CopyOnWriteArrayList<>(); - try(Stream packs = Files.list(packsDirectory)) { + try(Stream packs = getMemberPaths(packsDirectory)) { packs.parallel().forEach(path -> { try { ConfigPack pack = new ConfigPackImpl(path, platform); @@ -60,6 +61,17 @@ public synchronized void loadAll(Platform platform) throws IOException, PackLoad } } + @Override + public String getRegistryName() { + return "config"; + } + + @Override + public boolean validatePathIsMember(Path path) { + var file = path.toFile(); + return file.isDirectory() || file.getName().endsWith(".zip"); + } + public static class PackLoadFailuresException extends Exception { @Serial private static final long serialVersionUID = 538998844645186306L; diff --git a/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/MetaConfigRegistry.java b/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/MetaConfigRegistry.java index 05539475e5..9adeb0c711 100644 --- a/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/MetaConfigRegistry.java +++ b/common/implementation/base/src/main/java/com/dfsek/terra/registry/master/MetaConfigRegistry.java @@ -28,6 +28,7 @@ import com.dfsek.terra.api.config.MetaPack; import com.dfsek.terra.api.util.reflection.TypeKey; import com.dfsek.terra.config.pack.MetaPackImpl; +import com.dfsek.terra.registry.CliExtensibleRegistry; import com.dfsek.terra.registry.OpenRegistryImpl; import com.dfsek.terra.registry.master.ConfigRegistry.PackLoadFailuresException; @@ -35,7 +36,7 @@ /** * Class to hold config packs */ -public class MetaConfigRegistry extends OpenRegistryImpl { +public class MetaConfigRegistry extends OpenRegistryImpl implements CliExtensibleRegistry { public MetaConfigRegistry() { super(TypeKey.of(MetaPack.class)); @@ -45,8 +46,8 @@ public void loadAll(Platform platform, ConfigRegistry configRegistry) throws IOE Path packsDirectory = platform.getDataFolder().toPath().resolve("metapacks"); Files.createDirectories(packsDirectory); List failedLoads = new ArrayList<>(); - try(Stream packs = Files.list(packsDirectory)) { - packs.forEach(path -> { + try(Stream paths = getMemberPaths(packsDirectory)) { + paths.forEach(path -> { try { MetaPack pack = new MetaPackImpl(path, platform, configRegistry); registerChecked(pack.getRegistryKey(), pack); @@ -59,4 +60,15 @@ public void loadAll(Platform platform, ConfigRegistry configRegistry) throws IOE throw new PackLoadFailuresException(failedLoads); } } + + @Override + public String getRegistryName() { + return "metaconfig"; + } + + @Override + public boolean validatePathIsMember(Path path) { + var file = path.toFile(); + return file.isDirectory() || file.getName().endsWith(".zip"); + } }