diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java b/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java index 6aa0b197..7aac6513 100644 --- a/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/config/Config.java @@ -2,8 +2,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.spongepowered.configurate.NodePath; import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import xyz.jpenilla.squaremap.common.Logging; @SuppressWarnings("unused") public final class Config extends AbstractConfig { @@ -114,6 +116,22 @@ private static void progressLogging() { PROGRESS_LOGGING_INTERVAL = config.getInt("settings.render-progress-logging.interval-seconds", 1); } + public static DataFacilityType DATA_FACILITY_TYPE; + + private static void storage() { + try { + DATA_FACILITY_TYPE = DataFacilityType.valueOf( + config.getString("settings.storage.type", "flatfile").toUpperCase(Locale.ROOT) + ); + } catch (IllegalArgumentException e) { + Logging.logger().error("Invalid storage type '" + + config.getString("settings.storage.type", "flatfile") + + "', falling back to flatfile." + ); + DATA_FACILITY_TYPE = DataFacilityType.FLATFILE; + } + } + public static Config config() { return config; } diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/config/DataFacilityType.java b/common/src/main/java/xyz/jpenilla/squaremap/common/config/DataFacilityType.java new file mode 100644 index 00000000..8c291b67 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/config/DataFacilityType.java @@ -0,0 +1,5 @@ +package xyz.jpenilla.squaremap.common.config; + +public enum DataFacilityType { + FLATFILE +} diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java index b3ff8552..03dabb55 100644 --- a/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/MapWorldInternal.java @@ -1,19 +1,8 @@ package xyz.jpenilla.squaremap.common.data; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonIOException; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; -import java.io.BufferedReader; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -32,10 +21,10 @@ import xyz.jpenilla.squaremap.api.Registry; import xyz.jpenilla.squaremap.api.WorldIdentifier; import xyz.jpenilla.squaremap.common.LayerRegistry; -import xyz.jpenilla.squaremap.common.Logging; -import xyz.jpenilla.squaremap.common.SquaremapCommon; import xyz.jpenilla.squaremap.common.config.WorldAdvanced; import xyz.jpenilla.squaremap.common.config.WorldConfig; +import xyz.jpenilla.squaremap.common.data.facilities.DataFacility; +import xyz.jpenilla.squaremap.common.data.facilities.DataFacilityFactory; import xyz.jpenilla.squaremap.common.layer.SpawnIconProvider; import xyz.jpenilla.squaremap.common.layer.WorldBorderProvider; import xyz.jpenilla.squaremap.common.task.render.AbstractRender; @@ -43,22 +32,15 @@ import xyz.jpenilla.squaremap.common.task.render.FullRender; import xyz.jpenilla.squaremap.common.util.Colors; import xyz.jpenilla.squaremap.common.util.FileUtil; -import xyz.jpenilla.squaremap.common.util.RecordTypeAdapterFactory; import xyz.jpenilla.squaremap.common.util.Util; import xyz.jpenilla.squaremap.common.visibilitylimit.VisibilityLimitImpl; @DefaultQualifier(NonNull.class) public abstract class MapWorldInternal implements MapWorld { - private static final String DIRTY_CHUNKS_FILE_NAME = "dirty_chunks.json"; - private static final String RENDER_PROGRESS_FILE_NAME = "resume_render.json"; - private static final Gson GSON = new GsonBuilder() - .registerTypeAdapterFactory(new RecordTypeAdapterFactory()) - .enableComplexMapKeySerialization() - .create(); private static final Map LAYER_REGISTRIES = new HashMap<>(); private final ServerLevel level; - private final Path dataPath; + private final DataFacility dataFacility; private final Path tilesPath; private final ExecutorService imageIOexecutor; private final ScheduledExecutorService executor; @@ -90,16 +72,7 @@ protected MapWorldInternal(final ServerLevel level) { this.blockColors = new BlockColors(this); this.levelBiomeColorData = LevelBiomeColorData.create(this); - this.dataPath = SquaremapCommon.instance().platform().dataDirectory().resolve("data").resolve( - Util.levelWebName(this.level) - ); - try { - if (!Files.exists(this.dataPath)) { - Files.createDirectories(this.dataPath); - } - } catch (final IOException e) { - throw this.failedToCreateDataDirectory(e); - } + this.dataFacility = DataFacilityFactory.getDataFacility(this.identifier(), Util.levelWebName(this.level)); this.tilesPath = FileUtil.getAndCreateTilesDirectory(this.serverLevel()); @@ -124,53 +97,19 @@ protected MapWorldInternal(final ServerLevel level) { } public @Nullable Map getRenderProgress() { - try { - final Path file = this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME); - if (Files.isRegularFile(file)) { - final Type type = new TypeToken>() { - }.getType(); - try (final BufferedReader reader = Files.newBufferedReader(file)) { - return GSON.fromJson(reader, type); - } - } - } catch (JsonIOException | JsonSyntaxException | IOException e) { - Logging.logger().warn("Failed to deserialize render progress for world '{}'", this.identifier().asString(), e); - } - return null; + return this.dataFacility.getRenderProgress(); } public void saveRenderProgress(Map regions) { - try { - Files.writeString(this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME), GSON.toJson(regions)); - } catch (IOException e) { - Logging.logger().warn("Failed to serialize render progress for world '{}'", this.identifier().asString(), e); - } + this.dataFacility.saveRenderProgress(regions); } private void serializeDirtyChunks() { - try { - Files.writeString(this.dataPath.resolve(DIRTY_CHUNKS_FILE_NAME), GSON.toJson(this.modifiedChunks)); - } catch (IOException e) { - Logging.logger().warn("Failed to serialize dirty chunks for world '{}'", this.identifier().asString(), e); - } + this.dataFacility.saveDirtyChunks(this.modifiedChunks); } private void deserializeDirtyChunks() { - try { - final Path file = this.dataPath.resolve(DIRTY_CHUNKS_FILE_NAME); - if (Files.isRegularFile(file)) { - try (final BufferedReader reader = Files.newBufferedReader(file)) { - this.modifiedChunks.addAll( - GSON.fromJson( - reader, - TypeToken.getParameterized(List.class, ChunkCoordinate.class).getType() - ) - ); - } - } - } catch (JsonIOException | JsonSyntaxException | IOException e) { - Logging.logger().warn("Failed to deserialize dirty chunks for world '{}'", this.identifier().asString(), e); - } + this.modifiedChunks.addAll(this.dataFacility.getDirtyChunks()); } private void startBackgroundRender() { @@ -255,11 +194,7 @@ public void pauseRenders(boolean pauseRenders) { } public void finishedRender() { - try { - Files.deleteIfExists(this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME)); - } catch (IOException e) { - Logging.logger().warn("Failed to delete render progress data for world '{}'", this.identifier().asString(), e); - } + this.dataFacility.deleteRenderProgress(); } public void stopRender() { diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/DataFacility.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/DataFacility.java new file mode 100644 index 00000000..f413ddfd --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/DataFacility.java @@ -0,0 +1,21 @@ +package xyz.jpenilla.squaremap.common.data.facilities; + +import java.util.Map; +import java.util.Set; +import org.checkerframework.checker.nullness.qual.Nullable; +import xyz.jpenilla.squaremap.common.data.ChunkCoordinate; +import xyz.jpenilla.squaremap.common.data.RegionCoordinate; + +public interface DataFacility { + + @Nullable Map getRenderProgress(); + + void saveRenderProgress(Map renderProgress); + + void deleteRenderProgress(); + + Set getDirtyChunks(); + + void saveDirtyChunks(Set dirtyChunks); + +} diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/DataFacilityFactory.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/DataFacilityFactory.java new file mode 100644 index 00000000..920e5a6a --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/DataFacilityFactory.java @@ -0,0 +1,19 @@ +package xyz.jpenilla.squaremap.common.data.facilities; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import xyz.jpenilla.squaremap.api.WorldIdentifier; +import xyz.jpenilla.squaremap.common.config.Config; + +@DefaultQualifier(NonNull.class) +public final class DataFacilityFactory { + + public static DataFacility getDataFacility(WorldIdentifier identifier, String worldName) { + // todo + switch (Config.DATA_FACILITY_TYPE) { + case FLATFILE -> new FlatfileDataFacility(identifier, worldName); + } + return null; + } + +} diff --git a/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/FlatfileDataFacility.java b/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/FlatfileDataFacility.java new file mode 100644 index 00000000..f7bdb972 --- /dev/null +++ b/common/src/main/java/xyz/jpenilla/squaremap/common/data/facilities/FlatfileDataFacility.java @@ -0,0 +1,116 @@ +package xyz.jpenilla.squaremap.common.data.facilities; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import xyz.jpenilla.squaremap.api.WorldIdentifier; +import xyz.jpenilla.squaremap.common.Logging; +import xyz.jpenilla.squaremap.common.SquaremapCommon; +import xyz.jpenilla.squaremap.common.data.ChunkCoordinate; +import xyz.jpenilla.squaremap.common.data.RegionCoordinate; +import xyz.jpenilla.squaremap.common.util.RecordTypeAdapterFactory; + +@DefaultQualifier(NonNull.class) +public class FlatfileDataFacility implements DataFacility { + private static final String DIRTY_CHUNKS_FILE_NAME = "dirty_chunks.json"; + private static final String RENDER_PROGRESS_FILE_NAME = "resume_render.json"; + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapterFactory(new RecordTypeAdapterFactory()) + .enableComplexMapKeySerialization() + .create(); + + private final WorldIdentifier identifier; + private final Path dataPath; + + public FlatfileDataFacility(WorldIdentifier identifier, String worldName) { + this.identifier = identifier; + this.dataPath = SquaremapCommon.instance().platform().dataDirectory().resolve("data").resolve(worldName); + try { + if (!Files.exists(this.dataPath)) { + Files.createDirectories(this.dataPath); + } + } catch (final IOException e) { + throw new IllegalStateException(String.format("Failed to create data directory for world '%s'", this.identifier), e); + } + } + + @Override + public @Nullable Map getRenderProgress() { + try { + final Path file = this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME); + if (Files.isRegularFile(file)) { + final Type type = new TypeToken>() { + }.getType(); + try (final BufferedReader reader = Files.newBufferedReader(file)) { + return GSON.fromJson(reader, type); + } + } + } catch (JsonIOException | JsonSyntaxException | IOException e) { + Logging.logger().warn("Failed to deserialize render progress for world '{}'", this.identifier.asString(), e); + } + return null; + } + + @Override + public void saveRenderProgress(Map renderProgress) { + try { + Files.writeString(this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME), GSON.toJson(renderProgress)); + } catch (IOException e) { + Logging.logger().warn("Failed to serialize render progress for world '{}'", this.identifier.asString(), e); + } + } + + @Override + public void deleteRenderProgress() { + try { + Files.deleteIfExists(this.dataPath.resolve(RENDER_PROGRESS_FILE_NAME)); + } catch (IOException e) { + Logging.logger().warn("Failed to delete render progress data for world '{}'", this.identifier.asString(), e); + } + } + + @Override + public Set getDirtyChunks() { + Set ret = ConcurrentHashMap.newKeySet(); + try { + final Path file = this.dataPath.resolve(DIRTY_CHUNKS_FILE_NAME); + if (Files.isRegularFile(file)) { + try (final BufferedReader reader = Files.newBufferedReader(file)) { + ret.addAll( + GSON.fromJson( + reader, + TypeToken.getParameterized(List.class, ChunkCoordinate.class).getType() + ) + ); + } + } + } catch (JsonIOException | JsonSyntaxException | IOException e) { + Logging.logger().warn("Failed to deserialize dirty chunks for world '{}'", this.identifier.asString(), e); + } + return ret; + } + + @Override + public void saveDirtyChunks(Set dirtyChunks) { + try { + Files.writeString(this.dataPath.resolve(DIRTY_CHUNKS_FILE_NAME), GSON.toJson(dirtyChunks)); + } catch (IOException e) { + Logging.logger().warn("Failed to serialize dirty chunks for world '{}'", this.identifier.asString(), e); + } + } +}