Handle watching for region files even if the region folder doesnt yet exist on load time

This commit is contained in:
Lukas Rieger (Blue) 2024-06-11 18:24:29 +02:00
parent ad3c8fec60
commit e7b3bcb625
No known key found for this signature in database
GPG Key ID: AA33883B1BBA03E6
6 changed files with 70 additions and 18 deletions

View File

@ -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 {

View File

@ -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;
}
}
}

View File

@ -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<T> extends AutoCloseable {
* @throws ClosedException If the watch-service is closed
*/
@Nullable
List<T> poll();
List<T> poll() throws IOException;
/**
* Retrieves and consumes the next batch of events,
@ -49,7 +50,7 @@ public interface WatchService<T> 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<T> poll(long timeout, TimeUnit unit) throws InterruptedException;
@Nullable List<T> poll(long timeout, TimeUnit unit) throws IOException, InterruptedException;
/**
* Retrieves and consumes the next batch of events,
@ -57,7 +58,7 @@ public interface WatchService<T> 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<T> take() throws InterruptedException;
List<T> take() throws IOException, InterruptedException;
/**
* Thrown when the WatchService is closed or gets closed when polling or while waiting for events

View File

@ -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<Vector2i> listRegions() {
if (!Files.exists(regionFolder)) return Collections.emptyList();
try (Stream<Path> stream = Files.list(regionFolder)) {
return stream
.map(file -> {

View File

@ -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<Vector2i> {
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<Vector2i> poll() {
public @Nullable List<Vector2i> 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<Vector2i> poll(long timeout, TimeUnit unit) throws InterruptedException {
public @Nullable List<Vector2i> 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<Vector2i> take() throws InterruptedException {
public List<Vector2i> 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<Vector2i> 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();

View File

@ -101,8 +101,8 @@ public void renderMaps(BlueMapService blueMap, boolean watch, Predicate<TileStat
watcher.start();
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);
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)");