PostingsService.java
/*
* Copyright © 2016-2025 The CTAN Team and individual authors
*
* This file is distributed under the 3-clause BSD license.
* See file LICENSE for details.
*/
package org.ctan.site.services.postings;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
/**
* This class provides a service to read and watch the mailings on a local
* mailman mailing list (like ctan-ann).
*
* @author <a href="gene@ctan.org">Gerd Neugebauer</a>
*/
@Slf4j
public class PostingsService extends PostingCache {
/**
* The class <code>PostingsThread</code> contains the internal thread.
*/
class PostingsThread implements Runnable {
/**
* {@inheritDoc}
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
WatchService watcher;
try {
watcher = FileSystems.getDefault().newWatchService();
getBase().toPath().register(watcher,
ENTRY_CREATE,
ENTRY_MODIFY);
// java.nio.file.StandardWatchEventKinds.ENTRY_DELETE,
} catch (IOException ex) {
throw new IllegalArgumentException(
"watcher failed: " + ex.getMessage());
}
try {
for (;;) {
WatchKey key = watcher.take();
var needUpdate = false;
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == OVERFLOW) {
continue;
}
var filename = ((WatchEvent<Path>) event).context();
if (!filename.getName(filename.getNameCount() - 1)
.toString().endsWith(".txt.gz")) {
continue;
}
needUpdate = true;
break;
}
if (!key.reset()) {
break;
}
if (needUpdate) {
Thread.sleep(1000L);
update();
}
}
} catch (InterruptedException ex) {
log.error("watcher interrupted", ex);
} catch (IOException e) {
log.error("watcher update failed", e);
}
}
/**
* The method <code>start</code> provides means to run the watcher.
*/
public void start() {
new Thread(this).start();
}
}
/**
* This is the constructor for the class <code>PostingsService</code>.
*
* @param base the base directory to scan
* @throws IOException in case of an I/O error
* @throws FileNotFoundException in case of an error
*/
@SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW")
public PostingsService(@NonNull String base)
throws FileNotFoundException,
IOException {
super(new File(base));
}
/**
* This method starts the processing right after the service has been
* constructed.
*
* @return the thread
*/
public PostingsThread start() {
var postingsThread = new PostingsThread();
postingsThread.start();
return postingsThread;
}
}