107 lines
3.3 KiB
Java
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();
|
|
}
|
|
}
|