From e7b3bcb625c834fd7eab30240435d908dcea2715 Mon Sep 17 00:00:00 2001 From: "Lukas Rieger (Blue)" Date: Tue, 11 Jun 2024 18:24:29 +0200 Subject: [PATCH] Handle watching for region files even if the region folder doesnt yet exist on load time --- .../common/plugin/MapUpdateService.java | 2 + .../bluemap/core/util/FileHelper.java | 25 ++++++++++ .../bluemap/core/util/WatchService.java | 7 +-- .../bluemap/core/world/mca/MCAWorld.java | 2 + .../world/mca/MCAWorldRegionWatchService.java | 48 ++++++++++++++----- .../bluecolored/bluemap/cli/BlueMapCLI.java | 4 +- 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateService.java index 2db3ad43..c1ae6371 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/MapUpdateService.java @@ -67,6 +67,8 @@ public void run() { while (!closed) this.watchService.take().forEach(this::updateRegion); } catch (WatchService.ClosedException ignore) { + } catch (IOException e) { + Logger.global.logError("Exception trying to watch map '" + map.getId() + "' for updates.", e); } catch (InterruptedException iex) { Thread.currentThread().interrupt(); } finally { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java index 9f6696f6..b0b9ad4b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileHelper.java @@ -31,8 +31,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.URL; +import java.nio.file.WatchService; import java.nio.file.*; import java.nio.file.attribute.FileAttribute; +import java.util.concurrent.TimeUnit; public class FileHelper { @@ -114,4 +116,27 @@ public static void copy(URL source, Path target) throws IOException { } } + /** + * Uses file-watchers on the path-parent to wait until a specific file or folder exists + */ + public static boolean awaitExistence(Path path, long timeout, TimeUnit unit) throws IOException, InterruptedException { + if (Files.exists(path)) return true; + + long endTime = System.currentTimeMillis() + unit.toMillis(timeout); + + Path parent = path.toAbsolutePath().normalize().getParent(); + if (parent == null) throw new IOException("No parent directory exists that can be watched."); + if (!awaitExistence(parent, timeout, unit)) return false; + + try (WatchService watchService = parent.getFileSystem().newWatchService()) { + parent.register(watchService, StandardWatchEventKinds.ENTRY_CREATE); + while (!Files.exists(path)) { + long now = System.currentTimeMillis(); + if (now >= endTime) return false; + watchService.poll(endTime - now, TimeUnit.MILLISECONDS).reset(); + } + return true; + } + } + } 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 index e8954f7b..ac7a1f69 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WatchService.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WatchService.java @@ -27,6 +27,7 @@ import lombok.experimental.StandardException; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.util.List; import java.util.concurrent.TimeUnit; @@ -41,7 +42,7 @@ public interface WatchService extends AutoCloseable { * @throws ClosedException If the watch-service is closed */ @Nullable - List poll(); + List poll() throws IOException; /** * Retrieves and consumes the next batch of events, @@ -49,7 +50,7 @@ public interface WatchService extends AutoCloseable { * @throws ClosedException If the watch-service is closed, or it is closed while waiting for the next event * @throws InterruptedException If interrupted while waiting */ - @Nullable List poll(long timeout, TimeUnit unit) throws InterruptedException; + @Nullable List poll(long timeout, TimeUnit unit) throws IOException, InterruptedException; /** * Retrieves and consumes the next batch of events, @@ -57,7 +58,7 @@ public interface WatchService extends AutoCloseable { * @throws ClosedException If the watch-service is closed, or it is closed while waiting for the next event * @throws InterruptedException If interrupted while waiting */ - List take() throws InterruptedException; + List take() throws IOException, InterruptedException; /** * Thrown when the WatchService is closed or gets closed when polling or while waiting for events 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 3acf9381..7363d534 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 @@ -51,6 +51,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -162,6 +163,7 @@ private Region getRegion(Vector2i pos) { @Override public Collection listRegions() { + if (!Files.exists(regionFolder)) return Collections.emptyList(); try (Stream stream = Files.list(regionFolder)) { return stream .map(file -> { 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 index 91f585d1..db38c66f 100644 --- 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 @@ -25,34 +25,31 @@ package de.bluecolored.bluemap.core.world.mca; import com.flowpowered.math.vector.Vector2i; +import de.bluecolored.bluemap.core.util.FileHelper; 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.ClosedWatchServiceException; -import java.nio.file.Path; -import java.nio.file.StandardWatchEventKinds; -import java.nio.file.WatchKey; +import java.nio.file.*; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; public class MCAWorldRegionWatchService implements WatchService { + private final Path regionFolder; private final java.nio.file.WatchService watchService; + private boolean initialized; public MCAWorldRegionWatchService(Path regionFolder) throws IOException { + this.regionFolder = regionFolder; this.watchService = regionFolder.getFileSystem().newWatchService(); - regionFolder.register(this.watchService, - StandardWatchEventKinds.ENTRY_CREATE, - StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_DELETE - ); } @Override - public @Nullable List poll() { + public @Nullable List poll() throws IOException { + if (!ensureInitialization()) return null; try { WatchKey key = watchService.poll(); if (key == null) return null; @@ -63,9 +60,17 @@ public MCAWorldRegionWatchService(Path regionFolder) throws IOException { } @Override - public @Nullable List poll(long timeout, TimeUnit unit) throws InterruptedException { + public @Nullable List poll(long timeout, TimeUnit unit) throws IOException, InterruptedException { + long endTime = System.currentTimeMillis() + unit.toMillis(timeout); + + FileHelper.awaitExistence(regionFolder, timeout, unit); + if (!ensureInitialization()) return null; + + long now = System.currentTimeMillis(); + if (now >= endTime) return null; + try { - WatchKey key = watchService.poll(timeout, unit); + WatchKey key = watchService.poll(endTime - now, TimeUnit.MILLISECONDS); if (key == null) return null; return processWatchKey(key); } catch (ClosedWatchServiceException e) { @@ -74,7 +79,10 @@ public MCAWorldRegionWatchService(Path regionFolder) throws IOException { } @Override - public List take() throws InterruptedException { + public List take() throws IOException, InterruptedException { + while (!ensureInitialization()) + FileHelper.awaitExistence(regionFolder, 1, TimeUnit.HOURS); + try { WatchKey key = watchService.take(); return processWatchKey(key); @@ -83,6 +91,20 @@ public List take() throws InterruptedException { } } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private synchronized boolean ensureInitialization() throws IOException { + if (initialized) return true; + if (!Files.exists(regionFolder)) return false; + + regionFolder.register(this.watchService, + StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_MODIFY, + StandardWatchEventKinds.ENTRY_DELETE + ); + initialized = true; + return true; + } + @Override public void close() throws IOException { watchService.close(); 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 9beac5cd..ab26a536 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 @@ -101,8 +101,8 @@ public void renderMaps(BlueMapService blueMap, boolean watch, Predicate