diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java index 1b7dc5f6..d237bfe1 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java @@ -35,6 +35,8 @@ import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.storage.FileStorage; +import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.world.World; import org.apache.commons.io.FileUtils; @@ -164,11 +166,16 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc } } + Storage storage = new FileStorage( + getRenderConfig().getWebRoot().toPath().resolve("data"), + mapConfig.getCompression() + ); + BmMap map = new BmMap( id, name, world, - getRenderConfig().getWebRoot().toPath().resolve("data").resolve(id), + storage, getResourcePack(), mapConfig ); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java index 88900a01..aa3bfe74 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/api/render/RenderAPIImpl.java @@ -35,7 +35,6 @@ import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; import de.bluecolored.bluemap.common.rendermanager.RenderManager; import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask; -import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.world.Grid; import java.io.IOException; @@ -96,7 +95,7 @@ public boolean scheduleMapUpdateTask(BlueMapMap map, Collection region @Override public boolean scheduleMapPurgeTask(BlueMapMap map) throws IOException { BlueMapMapImpl cmap = castMap(map); - return renderManager.scheduleRenderTask(new MapPurgeTask(cmap.getMapType())); + return renderManager.scheduleRenderTask(MapPurgeTask.create(cmap.getMapType())); } @Override diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index f82bdb92..90a55ef2 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -64,7 +64,6 @@ import de.bluecolored.bluemap.core.world.World; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.function.Function; @@ -810,41 +809,33 @@ public int purgeCommand(CommandContext context) { CommandSource source = commandSourceInterface.apply(context.getSource()); // parse map argument - String mapId = context.getArgument("map", String.class); + String mapString = context.getArgument("map", String.class); + BmMap map = parseMap(mapString).orElse(null); + + if (map == null) { + source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); + return 0; + } new Thread(() -> { try { - Path mapFolder = plugin.getRenderConfig().getWebRoot().toPath().resolve("data").resolve(mapId); - if (!Files.isDirectory(mapFolder)) { - source.sendMessage(Text.of(TextColor.RED, "There is no map-data to purge for the map-id '" + mapId + "'!")); - return; - } - - Optional optMap = parseMap(mapId); - // delete map - MapPurgeTask purgeTask; - if (optMap.isPresent()){ - purgeTask = new MapPurgeTask(optMap.get()); - } else { - purgeTask = new MapPurgeTask(mapFolder); - } + MapPurgeTask purgeTask = MapPurgeTask.create(map); plugin.getRenderManager().scheduleRenderTaskNext(purgeTask); - source.sendMessage(Text.of(TextColor.GREEN, "Created new Task to purge map '" + mapId + "'")); + source.sendMessage(Text.of(TextColor.GREEN, "Created new Task to purge map '" + map.getId() + "'")); - // if map is loaded, reset it and start updating it after the purge - if (optMap.isPresent()) { - RenderTask updateTask = new MapUpdateTask(optMap.get()); - plugin.getRenderManager().scheduleRenderTask(updateTask); - source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + mapId + "'")); - source.sendMessage(Text.of(TextColor.GRAY, "If you don't want to render this map again, you need to remove it from your configuration first!")); - } + // reset the map and start updating it after the purge + RenderTask updateTask = new MapUpdateTask(map); + plugin.getRenderManager().scheduleRenderTask(updateTask); + source.sendMessage(Text.of(TextColor.GREEN, "Created new Update-Task for map '" + map.getId() + "'")); + source.sendMessage(Text.of(TextColor.GRAY, "If you don't this map to render again after the purge, use ", + TextColor.DARK_GRAY, "/bluemap freeze " + map.getId(), TextColor.GRAY, " first!")); source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress.")); } catch (IOException | IllegalArgumentException e) { - source.sendMessage(Text.of(TextColor.RED, "There was an error trying to purge '" + mapId + "', see console for details.")); - Logger.global.logError("Failed to purge map '" + mapId + "'!", e); + source.sendMessage(Text.of(TextColor.RED, "There was an error trying to purge '" + map.getId() + "', see console for details.")); + Logger.global.logError("Failed to purge map '" + map.getId() + "'!", e); } }).start(); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java index 59cacde4..d2e81f74 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/rendermanager/MapPurgeTask.java @@ -26,96 +26,179 @@ import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.map.BmMap; +import de.bluecolored.bluemap.core.storage.FileStorage; +import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.util.FileUtils; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.LinkedList; +import java.util.Objects; import java.util.stream.Collectors; -public class MapPurgeTask implements RenderTask { +public abstract class MapPurgeTask implements RenderTask { - @DebugDump private final BmMap map; - @DebugDump private final Path directory; - @DebugDump private final int subFilesCount; - private final LinkedList subFiles; - - @DebugDump private volatile boolean hasMoreWork; - @DebugDump private volatile boolean cancelled; - - public MapPurgeTask(Path mapDirectory) throws IOException { - this(null, mapDirectory); + public static MapPurgeTask create(BmMap map) throws IOException { + Storage storage = map.getStorage(); + if (storage instanceof FileStorage) { + return new MapFilePurgeTask(map, (FileStorage) storage); + } else { + return new MapStoragePurgeTask(map); + } } - public MapPurgeTask(BmMap map) throws IOException { - this(map, map.getFileRoot()); + public static MapPurgeTask create(Path mapDirectory) throws IOException { + return new MapFilePurgeTask(mapDirectory); } - private MapPurgeTask(BmMap map, Path directory) throws IOException { - this.map = map; - this.directory = directory; - this.subFiles = Files.walk(directory, 3) - .collect(Collectors.toCollection(LinkedList::new)); - this.subFilesCount = subFiles.size(); - this.hasMoreWork = true; - this.cancelled = false; + @DebugDump + private static class MapFilePurgeTask extends MapPurgeTask { + + private final BmMap map; + private final Path directory; + private final int subFilesCount; + private final LinkedList subFiles; + + private volatile boolean hasMoreWork; + private volatile boolean cancelled; + + public MapFilePurgeTask(Path mapDirectory) throws IOException { + this(null, mapDirectory); + } + + public MapFilePurgeTask(BmMap map, FileStorage fileStorage) throws IOException { + this(map, fileStorage.getFilePath(map.getId())); + } + + private MapFilePurgeTask(BmMap map, Path directory) throws IOException { + this.map = map; + this.directory = directory; + this.subFiles = Files.walk(directory, 3) + .collect(Collectors.toCollection(LinkedList::new)); + this.subFilesCount = subFiles.size(); + this.hasMoreWork = true; + this.cancelled = false; + } + + @Override + public void doWork() throws Exception { + synchronized (this) { + if (!this.hasMoreWork) return; + this.hasMoreWork = false; + } + + try { + // delete subFiles first to be able to track the progress and cancel + while (!subFiles.isEmpty()) { + Path subFile = subFiles.getLast(); + FileUtils.delete(subFile.toFile()); + subFiles.removeLast(); + if (this.cancelled) return; + } + + // make sure everything is deleted + FileUtils.delete(directory.toFile()); + } finally { + // reset map render state + if (this.map != null) { + this.map.getRenderState().reset(); + } + } + } + + @Override + public boolean hasMoreWork() { + return this.hasMoreWork; + } + + @Override + @DebugDump + public double estimateProgress() { + return 1d - (subFiles.size() / (double) subFilesCount); + } + + @Override + public void cancel() { + this.cancelled = true; + } + + @Override + public boolean contains(RenderTask task) { + if (task == this) return true; + if (task instanceof MapFilePurgeTask) { + return ((MapFilePurgeTask) task).directory.toAbsolutePath().normalize() + .startsWith(this.directory.toAbsolutePath().normalize()); + } + + return false; + } + + @Override + public String getDescription() { + return "Purge Map " + directory.getFileName(); + } + } - @Override - public void doWork() throws Exception { - synchronized (this) { - if (!this.hasMoreWork) return; + @DebugDump + private static class MapStoragePurgeTask extends MapPurgeTask { + + private final BmMap map; + + private volatile boolean hasMoreWork; + + public MapStoragePurgeTask(BmMap map) { + this.map = Objects.requireNonNull(map); + this.hasMoreWork = true; + } + + @Override + public void doWork() throws Exception { + synchronized (this) { + if (!this.hasMoreWork) return; + this.hasMoreWork = false; + } + + try { + map.getStorage().purgeMap(map.getId()); + } finally { + // reset map render state + map.getRenderState().reset(); + } + } + + @Override + public boolean hasMoreWork() { + return this.hasMoreWork; + } + + @Override + @DebugDump + public double estimateProgress() { + return 0d; + } + + @Override + public void cancel() { this.hasMoreWork = false; } - try { - // delete subFiles first to be able to track the progress and cancel - while (!subFiles.isEmpty()) { - Path subFile = subFiles.getLast(); - FileUtils.delete(subFile.toFile()); - subFiles.removeLast(); - if (this.cancelled) return; + @Override + public boolean contains(RenderTask task) { + if (task == this) return true; + if (task instanceof MapStoragePurgeTask) { + return map.equals(((MapStoragePurgeTask) task).map); } - // make sure everything is deleted - FileUtils.delete(directory.toFile()); - } finally { - // reset map render state - if (this.map != null) { - this.map.getRenderState().reset(); - } - } - } - - @Override - public boolean hasMoreWork() { - return this.hasMoreWork; - } - - @Override - public double estimateProgress() { - return 1d - (subFiles.size() / (double) subFilesCount); - } - - @Override - public void cancel() { - this.cancelled = true; - } - - @Override - public boolean contains(RenderTask task) { - if (task == this) return true; - if (task instanceof MapPurgeTask) { - return ((MapPurgeTask) task).directory.toAbsolutePath().normalize().startsWith(this.directory.toAbsolutePath().normalize()); + return false; + } + + @Override + public String getDescription() { + return "Purge Map " + map.getId(); } - return false; } - @Override - public String getDescription() { - return "Purge Map " + directory.getFileName(); - } - -} +} \ No newline at end of file diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java index 2dad48d0..2f05908a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MapConfig.java @@ -28,6 +28,7 @@ import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.map.MapSettings; +import de.bluecolored.bluemap.core.storage.Compression; import de.bluecolored.bluemap.core.util.ConfigUtils; import org.spongepowered.configurate.ConfigurationNode; @@ -53,7 +54,7 @@ public class MapConfig implements MapSettings { private Vector3i min, max; private boolean renderEdges; - private boolean useGzip; + private Compression compression; private boolean ignoreMissingLightData; private int hiresTileSize; @@ -106,7 +107,7 @@ public MapConfig(ConfigurationNode node) throws IOException { this.renderEdges = node.node("renderEdges").getBoolean(true); //useCompression - this.useGzip = node.node("useCompression").getBoolean(true); + this.compression = node.node("useCompression").getBoolean(true) ? Compression.GZIP : Compression.NONE; //ignoreMissingLightData this.ignoreMissingLightData = node.node("ignoreMissingLightData").getBoolean(false); @@ -142,6 +143,10 @@ public int getSkyColor() { return skyColor; } + public Compression getCompression() { + return compression; + } + @Override public float getAmbientLight() { return ambientLight; @@ -196,9 +201,4 @@ public boolean isRenderEdges() { return renderEdges; } - @Override - public boolean useGzipCompression() { - return useGzip; - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java index 19b63078..62bd4798 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/RenderConfig.java @@ -35,10 +35,10 @@ @DebugDump public class RenderConfig { - private File webRoot = new File("web"); + private File webRoot; private boolean useCookies; private boolean enableFreeFlight; - private List mapConfigs = new ArrayList<>(); + private List mapConfigs; public RenderConfig(ConfigurationNode node) throws IOException { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java index 4d60d210..e591d6ee 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java @@ -28,16 +28,19 @@ import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.hires.HiresModelManager; -import de.bluecolored.bluemap.core.map.lowres.LowresModelManager; import de.bluecolored.bluemap.core.map.hires.HiresTileMeta; +import de.bluecolored.bluemap.core.map.lowres.LowresModelManager; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.storage.*; import de.bluecolored.bluemap.core.world.Grid; import de.bluecolored.bluemap.core.world.World; -import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.file.Path; import java.util.Objects; +import java.util.Optional; import java.util.function.Predicate; @DebugDump @@ -46,7 +49,7 @@ public class BmMap { private final String id; private final String name; private final World world; - private final Path fileRoot; + private final Storage storage; private final MapRenderState renderState; @@ -58,38 +61,37 @@ public class BmMap { private long renderTimeSumNanos; private long tilesRendered; - public BmMap(String id, String name, World world, Path fileRoot, ResourcePack resourcePack, MapSettings settings) throws IOException { + public BmMap(String id, String name, World world, Storage storage, ResourcePack resourcePack, MapSettings settings) throws IOException { this.id = Objects.requireNonNull(id); this.name = Objects.requireNonNull(name); this.world = Objects.requireNonNull(world); - this.fileRoot = Objects.requireNonNull(fileRoot); + this.storage = Objects.requireNonNull(storage); Objects.requireNonNull(resourcePack); Objects.requireNonNull(settings); this.renderState = new MapRenderState(); - File rstateFile = getRenderStateFile(); - if (rstateFile.exists()) { - try { - this.renderState.load(rstateFile); + Optional rstateData = storage.readMeta(id, MetaType.RENDER_STATE); + if (rstateData.isPresent()) { + try (InputStream in = rstateData.get()){ + this.renderState.load(in); } catch (IOException ex) { Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex); } } this.hiresModelManager = new HiresModelManager( - fileRoot.resolve("hires"), + storage.tileStorage(id, TileType.HIRES), resourcePack, settings, new Grid(settings.getHiresTileSize(), 2) ); this.lowresModelManager = new LowresModelManager( - fileRoot.resolve("lowres"), + storage.tileStorage(id, TileType.LOWRES), new Vector2i(settings.getLowresPointsPerLowresTile(), settings.getLowresPointsPerLowresTile()), - new Vector2i(settings.getLowresPointsPerHiresTile(), settings.getLowresPointsPerHiresTile()), - settings.useGzipCompression() + new Vector2i(settings.getLowresPointsPerHiresTile(), settings.getLowresPointsPerHiresTile()) ); this.tileFilter = t -> true; @@ -116,17 +118,13 @@ public void renderTile(Vector2i tile) { public synchronized void save() { lowresModelManager.save(); - try { - this.renderState.save(getRenderStateFile()); + try (OutputStream out = storage.writeMeta(id, MetaType.RENDER_STATE)) { + this.renderState.save(out); } catch (IOException ex){ Logger.global.logError("Failed to save render-state for map: '" + this.id + "'!", ex); } } - public File getRenderStateFile() { - return fileRoot.resolve(".rstate").toFile(); - } - public String getId() { return id; } @@ -139,8 +137,8 @@ public World getWorld() { return world; } - public Path getFileRoot() { - return fileRoot; + public Storage getStorage() { + return storage; } public MapRenderState getRenderState() { @@ -189,7 +187,7 @@ public String toString() { "id='" + id + '\'' + ", name='" + name + '\'' + ", world=" + world + - ", fileRoot=" + fileRoot + + ", storage=" + storage + '}'; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java index e294c975..c1b303f3 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/MapRenderState.java @@ -26,7 +26,6 @@ import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.core.debug.DebugDump; -import de.bluecolored.bluemap.core.util.AtomicFileHelper; import java.io.*; import java.util.HashMap; @@ -57,12 +56,9 @@ public synchronized void reset() { regionRenderTimes.clear(); } - public synchronized void save(File file) throws IOException { - OutputStream fOut = AtomicFileHelper.createFilepartOutputStream(file); - GZIPOutputStream gOut = new GZIPOutputStream(fOut); - + public synchronized void save(OutputStream out) throws IOException { try ( - DataOutputStream dOut = new DataOutputStream(gOut) + DataOutputStream dOut = new DataOutputStream(new GZIPOutputStream(out)) ) { dOut.writeInt(regionRenderTimes.size()); @@ -79,13 +75,11 @@ public synchronized void save(File file) throws IOException { } } - public synchronized void load(File file) throws IOException { + public synchronized void load(InputStream in) throws IOException { regionRenderTimes.clear(); try ( - FileInputStream fIn = new FileInputStream(file); - GZIPInputStream gIn = new GZIPInputStream(fIn); - DataInputStream dIn = new DataInputStream(gIn) + DataInputStream dIn = new DataInputStream(new GZIPInputStream(in)) ) { int size = dIn.readInt(); @@ -98,7 +92,7 @@ public synchronized void load(File file) throws IOException { regionRenderTimes.put(regionPos, renderTime); } - } + } catch (EOFException ignore){} // ignoring a sudden end of stream, since it is save to only read as many as we can } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java index e0343302..dde5c1e2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java @@ -28,34 +28,27 @@ import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.util.AtomicFileHelper; -import de.bluecolored.bluemap.core.util.FileUtils; +import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.world.Grid; import de.bluecolored.bluemap.core.world.World; import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.zip.GZIPOutputStream; public class HiresModelManager { - private final Path fileRoot; + private final Storage.TileStorage storage; private final HiresModelRenderer renderer; private final Grid tileGrid; - private final boolean useGzip; - public HiresModelManager(Path fileRoot, ResourcePack resourcePack, RenderSettings renderSettings, Grid tileGrid) { - this(fileRoot, new HiresModelRenderer(resourcePack, renderSettings), tileGrid, renderSettings.useGzipCompression()); + public HiresModelManager(Storage.TileStorage storage, ResourcePack resourcePack, RenderSettings renderSettings, Grid tileGrid) { + this(storage, new HiresModelRenderer(resourcePack, renderSettings), tileGrid); } - public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Grid tileGrid, boolean useGzip) { - this.fileRoot = fileRoot; + public HiresModelManager(Storage.TileStorage storage, HiresModelRenderer renderer, Grid tileGrid) { + this.storage = storage; this.renderer = renderer; this.tileGrid = tileGrid; - - this.useGzip = useGzip; } /** @@ -79,25 +72,10 @@ public HiresTileMeta render(World world, Vector2i tile) { } private void save(final HiresTileModel model, Vector2i tile) { - File file = getFile(tile, useGzip); - - OutputStream os = null; - try { - os = AtomicFileHelper.createFilepartOutputStream(file); - os = new BufferedOutputStream(os); - if (useGzip) os = new GZIPOutputStream(os); - + try (OutputStream os = storage.write(tile)) { model.writeBufferGeometryJson(os); } catch (IOException e){ - Logger.global.logError("Failed to save hires model: " + file, e); - } finally { - try { - if (os != null) { - os.close(); - } - } catch (IOException e) { - Logger.global.logError("Failed to close file: " + file, e); - } + Logger.global.logError("Failed to save hires model: " + tile, e); } } @@ -108,11 +86,4 @@ public Grid getTileGrid() { return tileGrid; } - /** - * Returns the file for a tile - */ - public File getFile(Vector2i tilePos, boolean gzip){ - return FileUtils.coordsToFile(fileRoot, tilePos, "json" + (gzip ? ".gz" : "")); - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java index 74f80b00..86b07a67 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/RenderSettings.java @@ -73,13 +73,6 @@ default boolean isRenderEdges() { return true; } - /** - * If gzip compression will be used to compress the generated files - */ - default boolean useGzipCompression() { - return true; - } - default boolean isInsideRenderBoundaries(int x, int z) { Vector3i min = getMin(); Vector3i max = getMax(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java index 81c12278..3781041d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java @@ -26,16 +26,17 @@ import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3f; +import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.threejs.BufferGeometry; -import de.bluecolored.bluemap.core.util.AtomicFileHelper; import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.util.ModelUtils; -import java.io.*; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.zip.GZIPOutputStream; public class LowresModel { @@ -79,7 +80,7 @@ public void update(Vector2i point, float height, Vector3f color){ * Saves this model to its file * @param force if this is false, the model is only saved if it has any changes */ - public void save(File file, boolean force, boolean useGzip) throws IOException { + public void save(Storage.TileStorage storage, Vector2i tile, boolean force) throws IOException { if (!force && !hasUnsavedChanges) return; this.hasUnsavedChanges = false; @@ -91,11 +92,9 @@ public void save(File file, boolean force, boolean useGzip) throws IOException { } synchronized (fileLock) { - OutputStream os = new BufferedOutputStream(AtomicFileHelper.createFilepartOutputStream(file)); - if (useGzip) os = new GZIPOutputStream(os); - OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); try ( - PrintWriter pw = new PrintWriter(osw) + PrintWriter pw = new PrintWriter( + new OutputStreamWriter(storage.write(tile), StandardCharsets.UTF_8)) ){ pw.print(json); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java index f0897ca8..a0cf35f4 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java @@ -28,42 +28,36 @@ import com.flowpowered.math.vector.Vector3f; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.hires.HiresTileMeta; +import de.bluecolored.bluemap.core.storage.Storage; import de.bluecolored.bluemap.core.threejs.BufferGeometry; -import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.math.Color; import org.apache.commons.io.IOUtils; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import java.util.zip.GZIPInputStream; public class LowresModelManager { - private final Path fileRoot; + private final Storage.TileStorage storage; private final Vector2i pointsPerLowresTile; private final Vector2i pointsPerHiresTile; - private final boolean useGzip; - private final Map models; + private final Map models; - public LowresModelManager(Path fileRoot, Vector2i pointsPerLowresTile, Vector2i pointsPerHiresTile, boolean useGzip) { - this.fileRoot = fileRoot; + public LowresModelManager(Storage.TileStorage storage, Vector2i pointsPerLowresTile, Vector2i pointsPerHiresTile) { + this.storage = storage; this.pointsPerLowresTile = pointsPerLowresTile; this.pointsPerHiresTile = pointsPerHiresTile; models = new ConcurrentHashMap<>(); - - this.useGzip = useGzip; } /** @@ -116,7 +110,7 @@ public void render(HiresTileMeta tileMeta) { * Saves all unsaved changes to the models to disk */ public synchronized void save(){ - for (Entry entry : models.entrySet()){ + for (Entry entry : models.entrySet()){ saveModel(entry.getKey(), entry.getValue()); } @@ -159,47 +153,39 @@ public void update(int px, int pz, float height, Color color) { } } - /** - * Returns the file for a tile - */ - public File getFile(Vector2i tile, boolean useGzip){ - return FileUtils.coordsToFile(fileRoot, tile, "json" + (useGzip ? ".gz" : "")); - } - private LowresModel getModel(Vector2i tile) { - File modelFile = getFile(tile, useGzip); - CachedModel model = models.get(modelFile); + CachedModel model = models.get(tile); if (model == null){ synchronized (this) { - model = models.get(modelFile); + model = models.get(tile); if (model == null){ - if (modelFile.exists()){ - try (FileInputStream fis = new FileInputStream(modelFile)) { - InputStream is = fis; - if (useGzip) is = new GZIPInputStream(is); + try { + Optional optIs = storage.read(tile); + if (optIs.isPresent()){ + try (InputStream is = optIs.get()) { + String json = IOUtils.toString(is, StandardCharsets.UTF_8); - String json = IOUtils.toString(is, StandardCharsets.UTF_8); - - model = new CachedModel(BufferGeometry.fromJson(json)); - } catch (IllegalArgumentException | IOException ex){ - Logger.global.logWarning("Failed to load lowres model '" + modelFile + "': " + ex); - - try { - FileUtils.delete(modelFile); - } catch (IOException ex2) { - Logger.global.logError("Failed to delete lowres-file: " + modelFile, ex2); + model = new CachedModel(BufferGeometry.fromJson(json)); } } + } catch (IllegalArgumentException | IOException ex){ + Logger.global.logWarning("Failed to load lowres model '" + tile + "': " + ex); + + try { + storage.delete(tile); + } catch (IOException ex2) { + Logger.global.logError("Failed to delete lowres-file: " + tile, ex2); + } } if (model == null){ model = new CachedModel(pointsPerLowresTile); } - models.put(modelFile, model); + models.put(tile, model); tidyUpModelCache(); } @@ -217,12 +203,12 @@ private LowresModel getModel(Vector2i tile) { * This method gets automatically called if the cache grows, but if you want to ensure model will be saved after 2 minutes, you could e.g call this method every second.
*/ public synchronized void tidyUpModelCache() { - List> entries = new ArrayList<>(models.size()); + List> entries = new ArrayList<>(models.size()); entries.addAll(models.entrySet()); entries.sort((e1, e2) -> (int) Math.signum(e1.getValue().cacheTime - e2.getValue().cacheTime)); int size = entries.size(); - for (Entry e : entries) { + for (Entry e : entries) { if (size > 10) { saveAndRemoveModel(e.getKey(), e.getValue()); continue; @@ -234,22 +220,22 @@ public synchronized void tidyUpModelCache() { } } - private synchronized void saveAndRemoveModel(File modelFile, CachedModel model) { - models.remove(modelFile); + private synchronized void saveAndRemoveModel(Vector2i tile, CachedModel model) { + models.remove(tile); try { - model.save(modelFile, false, useGzip); + model.save(storage, tile,false); //logger.logDebug("Saved and unloaded lowres tile: " + model.getTile()); } catch (IOException ex) { - Logger.global.logError("Failed to save and unload lowres-model: " + modelFile, ex); + Logger.global.logError("Failed to save and unload lowres-model: " + tile, ex); } } - private void saveModel(File modelFile, CachedModel model) { + private void saveModel(Vector2i tile, CachedModel model) { try { - model.save(modelFile, false, useGzip); + model.save(storage, tile, false); //logger.logDebug("Saved lowres tile: " + model.getTile()); } catch (IOException ex) { - Logger.global.logError("Failed to save lowres-model: " + modelFile, ex); + Logger.global.logError("Failed to save lowres-model: " + tile, ex); } model.resetCacheTime(); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java new file mode 100644 index 00000000..2b4027ed --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Compression.java @@ -0,0 +1,75 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.storage; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +public enum Compression { + + NONE("") { + @Override + public OutputStream compress(OutputStream out) { + return out; + } + + @Override + public InputStream decompress(InputStream in) { + return in; + } + }, + + GZIP(".gz"){ + + @Override + public OutputStream compress(OutputStream out) throws IOException { + return new GZIPOutputStream(out); + } + + @Override + public InputStream decompress(InputStream in) throws IOException { + return new GZIPInputStream(in); + } + + }; + + private final String fileSuffix; + + Compression(String fileSuffix) { + this.fileSuffix = fileSuffix; + } + + public String getFileSuffix() { + return fileSuffix; + } + + public abstract OutputStream compress(OutputStream out) throws IOException; + + public abstract InputStream decompress(InputStream in) throws IOException; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/FileStorage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/FileStorage.java new file mode 100644 index 00000000..926ddaea --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/FileStorage.java @@ -0,0 +1,143 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.storage; + +import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.util.AtomicFileHelper; +import de.bluecolored.bluemap.core.util.FileUtils; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.*; + +@DebugDump +public class FileStorage extends Storage { + + private static final EnumMap metaTypeFileNames = new EnumMap<>(MetaType.class); + static { + metaTypeFileNames.put(MetaType.TEXTURES, "../textures.json"); + metaTypeFileNames.put(MetaType.SETTINGS, "../settings.json"); + metaTypeFileNames.put(MetaType.MARKERS, "../markers.json"); + metaTypeFileNames.put(MetaType.RENDER_STATE, ".rstate"); + } + + private final Path root; + private final Compression compression; + + public FileStorage(Path root, Compression compression) { + this.root = root; + this.compression = compression; + } + + @Override + public OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { + Path file = getFilePath(mapId, tileType, tile); + + OutputStream os = AtomicFileHelper.createFilepartOutputStream(file); + os = new BufferedOutputStream(os); + os = compression.compress(os); + + return os; + } + + @Override + public Optional readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { + Path file = getFilePath(mapId, tileType, tile); + + if (!Files.exists(file)) return Optional.empty(); + + InputStream is = Files.newInputStream(file, StandardOpenOption.READ); + is = new BufferedInputStream(is); + + return Optional.of(is); + } + + @Override + public void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException { + Path file = getFilePath(mapId, tileType, tile); + FileUtils.delete(file.toFile()); + } + + @Override + public OutputStream writeMeta(String mapId, MetaType metaType) throws IOException { + Path file = getFilePath(mapId).resolve(getFilename(metaType)); + + OutputStream os = AtomicFileHelper.createFilepartOutputStream(file); + os = new BufferedOutputStream(os); + + return os; + } + + @Override + public Optional readMeta(String mapId, MetaType metaType) throws IOException { + Path file = getFilePath(mapId).resolve(getFilename(metaType)); + + if (!Files.exists(file)) return Optional.empty(); + + InputStream is = Files.newInputStream(file, StandardOpenOption.READ); + is = new BufferedInputStream(is); + + return Optional.of(is); + } + + @Override + public void purgeMap(String mapId) throws IOException { + FileUtils.delete(getFilePath(mapId).toFile()); + } + + public Path getFilePath(String mapId, TileType tileType, Vector2i tile){ + String path = "x" + tile.getX() + "z" + tile.getY(); + char[] cs = path.toCharArray(); + List folders = new ArrayList<>(); + StringBuilder folder = new StringBuilder(); + for (char c : cs){ + folder.append(c); + if (c >= '0' && c <= '9'){ + folders.add(folder.toString()); + folder.delete(0, folder.length()); + } + } + String fileName = folders.remove(folders.size() - 1); + + Path p = getFilePath(mapId).resolve(tileType.getTypeId()); + for (String s : folders){ + p = p.resolve(s); + } + + return p.resolve(fileName + ".json" + compression.getFileSuffix()); + } + + public Path getFilePath(String mapId) { + return root.resolve(mapId); + } + + private static String getFilename(MetaType metaType) { + return metaTypeFileNames.getOrDefault(metaType, metaType.name().toLowerCase(Locale.ROOT)); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MetaType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MetaType.java new file mode 100644 index 00000000..54a26d31 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/MetaType.java @@ -0,0 +1,10 @@ +package de.bluecolored.bluemap.core.storage; + +public enum MetaType { + + TEXTURES, + SETTINGS, + MARKERS, + RENDER_STATE + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java new file mode 100644 index 00000000..d633a455 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/Storage.java @@ -0,0 +1,76 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.storage; + +import com.flowpowered.math.vector.Vector2i; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Optional; + +public abstract class Storage { + + public abstract OutputStream writeMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException; + + public abstract Optional readMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException; + + public abstract void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException; + + public abstract OutputStream writeMeta(String mapId, MetaType metaType) throws IOException; + + public abstract Optional readMeta(String mapId, MetaType metaType) throws IOException; + + public abstract void purgeMap(String mapId) throws IOException; + + public TileStorage tileStorage(final String mapId, final TileType tileType) { + return new TileStorage(mapId, tileType); + } + + public class TileStorage { + + private final String mapId; + private final TileType tileType; + + private TileStorage(String mapId, TileType tileType) { + this.mapId = mapId; + this.tileType = tileType; + } + + public OutputStream write(Vector2i tile) throws IOException { + return Storage.this.writeMapTile(mapId, tileType, tile); + } + + public Optional read(Vector2i tile) throws IOException { + return Storage.this.readMapTile(mapId, tileType, tile); + } + + public void delete(Vector2i tile) throws IOException { + deleteMapTile(mapId, tileType, tile); + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileType.java new file mode 100644 index 00000000..1981e44d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/storage/TileType.java @@ -0,0 +1,41 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.bluecolored.bluemap.core.storage; + +public enum TileType { + + HIRES ("hires"), + LOWRES ("lowres"); + + private final String typeId; + + TileType(String typeId) { + this.typeId = typeId; + } + + public String getTypeId() { + return typeId; + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java index d1bd48db..1ece7a94 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtils.java @@ -31,8 +31,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.regex.Pattern; public class FileUtils { diff --git a/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlugin.java b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlugin.java index 55f21cb1..fa15e0e2 100644 --- a/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlugin.java +++ b/implementations/sponge-8.0.0/src/main/java/de/bluecolored/bluemap/sponge8/SpongePlugin.java @@ -47,6 +47,7 @@ import org.spongepowered.api.config.ConfigDir; import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.block.TickBlockEvent; import org.spongepowered.api.event.lifecycle.RefreshGameEvent; import org.spongepowered.api.event.lifecycle.RegisterCommandEvent; import org.spongepowered.api.event.lifecycle.StartedEngineEvent;