diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateService.java similarity index 50% rename from BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java rename to BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateService.java index a788bed4..c75d0dc3 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/RegionFileWatchService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateService.java @@ -29,23 +29,20 @@ import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; -import de.bluecolored.bluemap.core.util.FileHelper; -import de.bluecolored.bluemap.core.world.World; -import de.bluecolored.bluemap.core.world.mca.MCAWorld; -import de.bluecolored.bluemap.core.world.mca.region.RegionType; +import de.bluecolored.bluemap.core.util.WatchService; import java.io.IOException; -import java.nio.file.*; +import java.nio.file.ClosedWatchServiceException; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; -public class RegionFileWatchService extends Thread { +public class MapUpdateService extends Thread { private final BmMap map; private final RenderManager renderManager; - private final WatchService watchService; + private final WatchService watchService; private volatile boolean closed; @@ -53,25 +50,12 @@ public class RegionFileWatchService extends Thread { private final Map scheduledUpdates; - public RegionFileWatchService(RenderManager renderManager, BmMap map) throws IOException { + public MapUpdateService(RenderManager renderManager, BmMap map) throws IOException { this.renderManager = renderManager; this.map = map; this.closed = false; this.scheduledUpdates = new HashMap<>(); - - World world = map.getWorld(); - if (!(world instanceof MCAWorld)) throw new UnsupportedOperationException("world-type is not supported"); - Path folder = ((MCAWorld) world).getRegionFolder(); - FileHelper.createDirectories(folder); - - this.watchService = folder.getFileSystem().newWatchService(); - folder.register(this.watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_DELETE - ); - - Logger.global.logDebug("Created region-file watch-service for map '" + map.getId() + "' at '" + folder + "'."); + this.watchService = map.getWorld().createRegionWatchService(); } @Override @@ -81,24 +65,8 @@ public void run() { Logger.global.logDebug("Started watching map '" + map.getId() + "' for updates..."); try { - while (!closed) { - WatchKey key = this.watchService.take(); - - for (WatchEvent event : key.pollEvents()) { - WatchEvent.Kind kind = event.kind(); - - if (kind == StandardWatchEventKinds.OVERFLOW) continue; - - Object fileObject = event.context(); - if (!(fileObject instanceof Path)) continue; - Path file = (Path) fileObject; - - String regionFileName = file.toFile().getName(); - updateRegion(regionFileName); - } - - if (!key.reset()) return; - } + while (!closed) + this.watchService.take().forEach(this::updateRegion); } catch (ClosedWatchServiceException ignore) { } catch (InterruptedException iex) { Thread.currentThread().interrupt(); @@ -111,36 +79,25 @@ public void run() { } } - private synchronized void updateRegion(String regionFileName) { - if (RegionType.forFileName(regionFileName) == null) return; + private synchronized void updateRegion(Vector2i regionPos) { + // we only want to start the render when there were no changes on a file for 5 seconds + TimerTask task = scheduledUpdates.remove(regionPos); + if (task != null) task.cancel(); - try { - String[] filenameParts = regionFileName.split("\\."); - if (filenameParts.length < 3) return; + task = new TimerTask() { + @Override + public void run() { + synchronized (MapUpdateService.this) { + WorldRegionRenderTask task = new WorldRegionRenderTask(map, regionPos); + scheduledUpdates.remove(regionPos); + renderManager.scheduleRenderTask(task); - int rX = Integer.parseInt(filenameParts[1]); - int rZ = Integer.parseInt(filenameParts[2]); - Vector2i regionPos = new Vector2i(rX, rZ); - - // we only want to start the render when there were no changes on a file for 5 seconds - TimerTask task = scheduledUpdates.remove(regionPos); - if (task != null) task.cancel(); - - task = new TimerTask() { - @Override - public void run() { - synchronized (RegionFileWatchService.this) { - WorldRegionRenderTask task = new WorldRegionRenderTask(map, regionPos); - scheduledUpdates.remove(regionPos); - renderManager.scheduleRenderTask(task); - - Logger.global.logDebug("Scheduled update for region-file: " + regionPos + " (Map: " + map.getId() + ")"); - } + Logger.global.logDebug("Scheduled update for region-file: " + regionPos + " (Map: " + map.getId() + ")"); } - }; - scheduledUpdates.put(regionPos, task); - delayTimer.schedule(task, 5000); - } catch (NumberFormatException ignore) {} + } + }; + scheduledUpdates.put(regionPos, task); + delayTimer.schedule(task, 5000); } public void close() { @@ -151,7 +108,7 @@ public void close() { try { this.watchService.close(); - } catch (IOException ex) { + } catch (Exception ex) { Logger.global.logError("Exception while trying to close WatchService!", ex); } } diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index fcd09e07..ba5aeb5e 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -94,7 +94,7 @@ public class Plugin implements ServerEventListener { private Timer daemonTimer; - private Map regionFileWatchServices; + private Map mapUpdateServices; private PlayerSkinUpdater skinUpdater; @@ -316,8 +316,8 @@ public void run() { TimerTask fileWatcherRestartTask = new TimerTask() { @Override public void run() { - regionFileWatchServices.values().forEach(RegionFileWatchService::close); - regionFileWatchServices.clear(); + mapUpdateServices.values().forEach(MapUpdateService::close); + mapUpdateServices.clear(); initFileWatcherTasks(); } }; @@ -351,7 +351,7 @@ public void run() { daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30)); //watch map-changes - this.regionFileWatchServices = new HashMap<>(); + this.mapUpdateServices = new HashMap<>(); initFileWatcherTasks(); //register listener @@ -408,11 +408,11 @@ public void unload(boolean keepWebserver) { daemonTimer = null; //stop file-watchers - if (regionFileWatchServices != null) { - regionFileWatchServices.values().forEach(RegionFileWatchService::close); - regionFileWatchServices.clear(); + if (mapUpdateServices != null) { + mapUpdateServices.values().forEach(MapUpdateService::close); + mapUpdateServices.clear(); } - regionFileWatchServices = null; + mapUpdateServices = null; // stop render-manager if (renderManager != null){ @@ -567,16 +567,20 @@ public synchronized void startWatchingMap(BmMap map) { stopWatchingMap(map); try { - RegionFileWatchService watcher = new RegionFileWatchService(renderManager, map); + MapUpdateService watcher = new MapUpdateService(renderManager, map); watcher.start(); - regionFileWatchServices.put(map.getId(), watcher); + mapUpdateServices.put(map.getId(), watcher); } catch (IOException ex) { - Logger.global.logError("Failed to create file-watcher for map: " + map.getId() + " (This means the map might not automatically update)", ex); + Logger.global.logError("Failed to create update-watcher for map: " + map.getId() + + " (This means the map might not automatically update)", ex); + } catch (UnsupportedOperationException ex) { + Logger.global.logWarning("Update-watcher for map '" + map.getId() + "' is not supported for the world-type." + + " (This means the map might not automatically update)"); } } public synchronized void stopWatchingMap(BmMap map) { - RegionFileWatchService watcher = regionFileWatchServices.remove(map.getId()); + MapUpdateService watcher = mapUpdateServices.remove(map.getId()); if (watcher != null) { watcher.close(); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WatchService.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WatchService.java new file mode 100644 index 00000000..48235b24 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WatchService.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * A watch service that watches for changes and events. + * @param The type of the events or changes this WatchService provides + */ +public interface WatchService extends AutoCloseable { + + @Nullable + List poll(); + + @Nullable List poll(long timeout, TimeUnit unit) throws InterruptedException; + + List take() throws InterruptedException; + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java index 8015bd6f..2f02e300 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -27,7 +27,9 @@ import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.util.Grid; +import de.bluecolored.bluemap.core.util.WatchService; +import java.io.IOException; import java.util.Collection; import java.util.function.Predicate; @@ -72,6 +74,15 @@ public interface World { */ Collection listRegions(); + /** + * Creates and returns a new {@link WatchService} which watches for any changes in this worlds regions. + * @throws IOException if an IOException occurred while creating the watch-service + * @throws UnsupportedOperationException if watching this world is not supported + */ + default WatchService createRegionWatchService() throws IOException { + throw new UnsupportedOperationException(); + } + /** * Loads all chunks from the specified region into the chunk cache (if there is a cache) */ @@ -79,6 +90,9 @@ default void preloadRegionChunks(int x, int z) { preloadRegionChunks(x, z, pos -> true); } + /** + * Loads the filtered chunks from the specified region into the chunk cache (if there is a cache) + */ void preloadRegionChunks(int x, int z, Predicate chunkFilter); /** @@ -91,9 +105,4 @@ default void preloadRegionChunks(int x, int z) { */ void invalidateChunkCache(int x, int z); - /** - * Cleans up invalid cache-entries to free up memory - */ - void cleanUpChunkCache(); - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java index 8cc24cc1..bc0ec667 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorld.java @@ -36,6 +36,7 @@ import de.bluecolored.bluemap.core.util.Grid; import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.util.Vector2iCache; +import de.bluecolored.bluemap.core.util.WatchService; import de.bluecolored.bluemap.core.world.*; import de.bluecolored.bluemap.core.world.mca.chunk.ChunkLoader; import de.bluecolored.bluemap.core.world.mca.data.DimensionTypeDeserializer; @@ -165,21 +166,11 @@ public Collection listRegions() { return stream .map(file -> { try { - String fileName = file.getFileName().toString(); - - if (RegionType.forFileName(fileName) == null) return null; if (Files.size(file) <= 0) return null; - - String[] filenameParts = fileName.split("\\."); - int rX = Integer.parseInt(filenameParts[1]); - int rZ = Integer.parseInt(filenameParts[2]); - - return new Vector2i(rX, rZ); + return RegionType.regionForFileName(file.getFileName().toString()); } catch (IOException ex) { Logger.global.logError("Failed to read region-file: " + file, ex); return null; - } catch (NumberFormatException ignore) { - return null; } }) .filter(Objects::nonNull) @@ -190,6 +181,11 @@ public Collection listRegions() { } } + @Override + public WatchService createRegionWatchService() throws IOException { + return new MCAWorldRegionWatchService(this.regionFolder); + } + @Override public void preloadRegionChunks(int x, int z, Predicate chunkFilter) { try { @@ -223,11 +219,6 @@ public void invalidateChunkCache(int x, int z) { chunkCache.invalidate(VECTOR_2_I_CACHE.get(x, z)); } - @Override - public void cleanUpChunkCache() { - chunkCache.cleanUp(); - } - private Region loadRegion(Vector2i regionPos) { return loadRegion(regionPos.getX(), regionPos.getY()); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorldRegionWatchService.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorldRegionWatchService.java new file mode 100644 index 00000000..b01455df --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/MCAWorldRegionWatchService.java @@ -0,0 +1,95 @@ +/* + * 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.world.mca; + +import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.util.WatchService; +import de.bluecolored.bluemap.core.world.mca.region.RegionType; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchKey; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class MCAWorldRegionWatchService implements WatchService { + + private final java.nio.file.WatchService watchService; + + public MCAWorldRegionWatchService(Path regionFolder) throws IOException { + this.watchService = regionFolder.getFileSystem().newWatchService(); + regionFolder.register(this.watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_DELETE + ); + } + + @Override + public @Nullable List poll() { + WatchKey key = watchService.poll(); + if (key == null) return null; + return processWatchKey(key); + } + + @Override + public @Nullable List poll(long timeout, TimeUnit unit) throws InterruptedException { + WatchKey key = watchService.poll(timeout, unit); + if (key == null) return null; + return processWatchKey(key); + } + + @Override + public List take() throws InterruptedException { + WatchKey key = watchService.take(); + return processWatchKey(key); + } + + @Override + public void close() throws IOException { + watchService.close(); + } + + private List processWatchKey(WatchKey key) { + try { + return key.pollEvents().stream() + .map(event -> { + if (event.context() instanceof Path path) { + return RegionType.regionForFileName(path.getFileName().toString()); + } else { + return null; + } + }) + .filter(Objects::nonNull) + .toList(); + } finally { + key.reset(); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java index d1294dc2..24cf5e98 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/LinearRegion.java @@ -36,6 +36,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.regex.Pattern; /* * LinearFormat: @@ -63,6 +64,7 @@ public class LinearRegion implements Region { public static final String FILE_SUFFIX = ".linear"; + public static final Pattern FILE_PATTERN = Pattern.compile("^r\\.(-?\\d+)\\.(-?\\d+)\\.linear$"); private static final long MAGIC = 0xc3ff13183cca9d9aL; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java index d1e420bc..69ec90f2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/MCARegion.java @@ -40,11 +40,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.regex.Pattern; @Getter public class MCARegion implements Region { public static final String FILE_SUFFIX = ".mca"; + public static final Pattern FILE_PATTERN = Pattern.compile("^r\\.(-?\\d+)\\.(-?\\d+)\\.mca$"); + public static final Compression[] CHUNK_COMPRESSION_MAP = new Compression[255]; static { CHUNK_COMPRESSION_MAP[0] = Compression.NONE; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java index 85956eaf..00394346 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/mca/region/RegionType.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.world.mca.region; +import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.core.util.Key; import de.bluecolored.bluemap.core.util.Keyed; import de.bluecolored.bluemap.core.util.Registry; @@ -31,16 +32,17 @@ import de.bluecolored.bluemap.core.world.mca.MCAWorld; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.nio.file.Files; import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public interface RegionType extends Keyed { - RegionType MCA = new Impl(Key.bluemap("mca"), MCARegion.FILE_SUFFIX, MCARegion::new, MCARegion::getRegionFileName); - RegionType LINEAR = new Impl(Key.bluemap("linear"), LinearRegion.FILE_SUFFIX, LinearRegion::new, LinearRegion::getRegionFileName); + RegionType MCA = new Impl(Key.bluemap("mca"), MCARegion::new, MCARegion::getRegionFileName, MCARegion.FILE_PATTERN); + RegionType LINEAR = new Impl(Key.bluemap("linear"), LinearRegion::new, LinearRegion::getRegionFileName, LinearRegion.FILE_PATTERN); RegionType DEFAULT = MCA; Registry REGISTRY = new Registry<>( @@ -48,36 +50,55 @@ public interface RegionType extends Keyed { LINEAR ); - String getFileSuffix(); - + /** + * Creates a new {@link Region} from the given world and region-file + */ Region createRegion(MCAWorld world, Path regionFile); - Path getRegionFile(Path regionFolder, int regionX, int regionZ); + /** + * Converts region coordinates into the region-file name. + */ + String getRegionFileName(int regionX, int regionZ); + + /** + * Converts the region-file name into region coordinates. + * Returns null if the name does not match the expected format. + */ + @Nullable Vector2i getRegionFromFileName(String fileName); static @Nullable RegionType forFileName(String fileName) { for (RegionType regionType : REGISTRY.values()) { - if (fileName.endsWith(regionType.getFileSuffix())) + if (regionType.getRegionFromFileName(fileName) != null) return regionType; } return null; } - static @NotNull Region loadRegion(MCAWorld world, Path regionFolder, int regionX, int regionZ) { + static @Nullable Vector2i regionForFileName(String fileName) { for (RegionType regionType : REGISTRY.values()) { - Path regionFile = regionType.getRegionFile(regionFolder, regionX, regionZ); + Vector2i pos = regionType.getRegionFromFileName(fileName); + if (pos != null) return pos; + } + + return null; + } + + static Region loadRegion(MCAWorld world, Path regionFolder, int regionX, int regionZ) { + for (RegionType regionType : REGISTRY.values()) { + Path regionFile = regionFolder.resolve(regionType.getRegionFileName(regionX, regionZ)); if (Files.exists(regionFile)) return regionType.createRegion(world, regionFile); } - return DEFAULT.createRegion(world, DEFAULT.getRegionFile(regionFolder, regionX, regionZ)); + return DEFAULT.createRegion(world, regionFolder.resolve(DEFAULT.getRegionFileName(regionX, regionZ))); } @RequiredArgsConstructor class Impl implements RegionType { @Getter private final Key key; - @Getter private final String fileSuffix; private final RegionFactory regionFactory; private final RegionFileNameFunction regionFileNameFunction; + private final Pattern regionFileNamePattern; public Region createRegion(MCAWorld world, Path regionFile) { return this.regionFactory.create(world, regionFile); @@ -87,8 +108,14 @@ public String getRegionFileName(int regionX, int regionZ) { return regionFileNameFunction.getRegionFileName(regionX, regionZ); } - public Path getRegionFile(Path regionFolder, int regionX, int regionZ) { - return regionFolder.resolve(getRegionFileName(regionX, regionZ)); + @Override + public @Nullable Vector2i getRegionFromFileName(String fileName) { + Matcher matcher = regionFileNamePattern.matcher(fileName); + if (!matcher.matches()) return null; + return new Vector2i( + Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(2)) + ); } } diff --git a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index 026445a1..7872b067 100644 --- a/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/implementations/cli/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -31,7 +31,7 @@ import de.bluecolored.bluemap.common.config.ConfigurationException; import de.bluecolored.bluemap.common.config.CoreConfig; import de.bluecolored.bluemap.common.config.WebserverConfig; -import de.bluecolored.bluemap.common.plugin.RegionFileWatchService; +import de.bluecolored.bluemap.common.plugin.MapUpdateService; import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask; import de.bluecolored.bluemap.common.rendermanager.RenderManager; import de.bluecolored.bluemap.common.rendermanager.RenderTask; @@ -88,16 +88,19 @@ public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRende Map maps = blueMap.getOrLoadMaps(mapFilter); //watcher - List regionFileWatchServices = new ArrayList<>(); + List mapUpdateServices = new ArrayList<>(); if (watch) { for (BmMap map : maps.values()) { try { - RegionFileWatchService watcher = new RegionFileWatchService(renderManager, map); + MapUpdateService watcher = new MapUpdateService(renderManager, map); watcher.start(); - regionFileWatchServices.add(watcher); + mapUpdateServices.add(watcher); } catch (IOException ex) { Logger.global.logError("Failed to create file-watcher for map: " + map.getId() + - " (This map might not automatically update)", ex); + " (This map might not automatically update)", ex); + } catch (UnsupportedOperationException ex) { + Logger.global.logWarning("Update-watcher for map '" + map.getId() + "' is not supported for the world-type." + + " (This means the map might not automatically update)"); } } } @@ -150,8 +153,8 @@ public void run() { updateInfoTask.cancel(); saveTask.cancel(); - regionFileWatchServices.forEach(RegionFileWatchService::close); - regionFileWatchServices.clear(); + mapUpdateServices.forEach(MapUpdateService::close); + mapUpdateServices.clear(); renderManager.removeAllRenderTasks(); try {