Files
ProcessControl/SIN.04028-From-Calling-to-Eventing/supplemental-java/05-dynamic-filesystem/FileSubscriptions.java
T

107 lines
3.3 KiB
Java

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
record IncomingMessage(String mailbox, Message message) {}
final class FileSubscriptions implements AutoCloseable {
private final WatchService watcher;
private final Map<String, WatchKey> subscriptions = new ConcurrentHashMap<>();
private final Map<WatchKey, String> mailboxes = new ConcurrentHashMap<>();
private final BlockingQueue<IncomingMessage> messages =
new LinkedBlockingQueue<>();
private final Thread observer;
FileSubscriptions() throws IOException {
watcher = FileSystems.getDefault().newWatchService();
observer = Thread.ofVirtual().start(this::observe);
}
void subscribe(String mailbox) throws IOException {
if (subscriptions.containsKey(mailbox))
return;
var directory = FileMessageBoard.directory(mailbox);
Files.createDirectories(directory);
var key = directory.register(
watcher,
StandardWatchEventKinds.ENTRY_CREATE
);
subscriptions.put(mailbox, key);
mailboxes.put(key, mailbox);
}
void unsubscribe(String mailbox) {
var key = subscriptions.remove(mailbox);
if (key != null) {
mailboxes.remove(key);
key.cancel();
}
}
IncomingMessage take() throws InterruptedException {
return messages.take();
}
private void observe() {
try {
while (true) {
var key = watcher.take();
var mailbox = mailboxes.get(key);
if (mailbox == null) {
key.reset();
continue;
}
var directory = FileMessageBoard.directory(mailbox);
for (var event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.OVERFLOW)
continue;
var filename = (Path) event.context();
if (!filename.toString().endsWith(".msg"))
continue;
var file = directory.resolve(filename);
if (Files.isRegularFile(file))
messages.put(
new IncomingMessage(mailbox, FileMessageBoard.read(file))
);
}
if (!key.reset()) {
subscriptions.remove(mailbox);
mailboxes.remove(key);
}
}
} catch (ClosedWatchServiceException _) {
// close() ends the observer.
} catch (InterruptedException _) {
Thread.currentThread().interrupt();
} catch (IOException exception) {
throw new UncheckedIOException(exception);
}
}
public void close() throws IOException {
watcher.close();
observer.interrupt();
}
}