java - ¿Puedo ver el cambio de un solo archivo con WatchService(no el directorio completo)?
(7)
Apache ofrece una clase FileWatchDog con un método doOnChange
.
private class SomeWatchFile extends FileWatchdog {
protected SomeWatchFile(String filename) {
super(filename);
}
@Override
protected void doOnChange() {
fileChanged= true;
}
}
Y donde quieras, puedes comenzar este hilo:
SomeWatchFile someWatchFile = new SomeWatchFile (path);
someWatchFile.start();
La clase FileWatchDog sondea la lastModified()
marca de tiempo de un archivo lastModified()
. El WatchService nativo de Java NIO es más eficiente, ya que las notificaciones son inmediatas.
Cuando intento registrar un archivo en lugar de un directorio, se lanza java.nio.file.NotDirectoryException
. ¿Puedo escuchar un solo cambio de archivo, no todo el directorio?
He creado un envoltorio alrededor del WatchService
Java 1.7 que permite registrar un directorio y cualquier cantidad de patrones glob . Esta clase se encargará del filtrado y solo emitirá los eventos que te interesen.
try {
DirectoryWatchService watchService = new SimpleDirectoryWatchService(); // May throw
watchService.register( // May throw
new DirectoryWatchService.OnFileChangeListener() {
@Override
public void onFileCreate(String filePath) {
// File created
}
@Override
public void onFileModify(String filePath) {
// File modified
}
@Override
public void onFileDelete(String filePath) {
// File deleted
}
},
<directory>, // Directory to watch
<file-glob-pattern-1>, // E.g. "*.log"
<file-glob-pattern-2>, // E.g. "input-?.txt"
<file-glob-pattern-3>, // E.g. "config.ini"
... // As many patterns as you like
);
watchService.start(); // The actual watcher runs on a new thread
} catch (IOException e) {
LOGGER.error("Unable to register file change listener for " + fileName);
}
El código completo está en este repo .
No estoy seguro de los demás, pero gimo por la cantidad de código necesario para ver un solo archivo en busca de cambios utilizando la API WatchService básica. ¡Tiene que ser más simple!
Aquí hay un par de alternativas usando bibliotecas de terceros:
- Usando la configuración de Apache Commons
- Utilizando un paquete de Spring-loaded de Spring Framework (no encontré una implementación de ejemplo para esto, pero parece sencillo de usar)
No puede ver un archivo individual directamente, pero puede filtrar lo que no necesita.
Aquí está mi implementación de la clase FileWatcher
:
import java.io.File;
import java.nio.file.*;
import java.nio.file.WatchEvent.Kind;
import static java.nio.file.StandardWatchEventKinds.*;
public abstract class FileWatcher
{
private Path folderPath;
private String watchFile;
public FileWatcher(String watchFile)
{
Path filePath = Paths.get(watchFile);
boolean isRegularFile = Files.isRegularFile(filePath);
if (!isRegularFile)
{
// Do not allow this to be a folder since we want to watch files
throw new IllegalArgumentException(watchFile + " is not a regular file");
}
// This is always a folder
folderPath = filePath.getParent();
// Keep this relative to the watched folder
this.watchFile = watchFile.replace(folderPath.toString() + File.separator, "");
}
public void watchFile() throws Exception
{
// We obtain the file system of the Path
FileSystem fileSystem = folderPath.getFileSystem();
// We create the new WatchService using the try-with-resources block
try (WatchService service = fileSystem.newWatchService())
{
// We watch for modification events
folderPath.register(service, ENTRY_MODIFY);
// Start the infinite polling loop
while (true)
{
// Wait for the next event
WatchKey watchKey = service.take();
for (WatchEvent<?> watchEvent : watchKey.pollEvents())
{
// Get the type of the event
Kind<?> kind = watchEvent.kind();
if (kind == ENTRY_MODIFY)
{
Path watchEventPath = (Path) watchEvent.context();
// Call this if the right file is involved
if (watchEventPath.toString().equals(watchFile))
{
onModified();
}
}
}
if (!watchKey.reset())
{
// Exit if no longer valid
break;
}
}
}
}
public abstract void onModified();
}
Para usar esto, solo tienes que extender e implementar el método onModified()
manera:
import java.io.File;
public class MyFileWatcher extends FileWatcher
{
public MyFileWatcher(String watchFile)
{
super(watchFile);
}
@Override
public void onModified()
{
System.out.println("Modified!");
}
}
Finalmente, comienza a mirar el archivo:
String watchFile = System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "Test.txt";
FileWatcher fileWatcher = new MyFileWatcher(watchFile);
fileWatcher.watchFile();
No, no es posible registrar un archivo, el servicio de vigilancia no funciona de esta manera. Pero el registro de un directorio realmente observa los cambios en el directorio hijos (los archivos y subdirectorios), no los cambios en el directorio en sí.
Si desea ver un archivo, entonces registra el directorio que lo contiene con el servicio de vigilancia. La documentación de Path.register () dice:
WatchKey java.nio.file.Path.register (watchService watcher, Kind [] events, Modifier ... modificadores) lanza IOException
Registra el archivo ubicado en esta ruta con un servicio de vigilancia.
En esta versión, esta ruta localiza un directorio que existe. El directorio está registrado con el servicio de vigilancia para poder ver las entradas en el directorio
Luego debe procesar los eventos en las entradas y detectar los relacionados con el archivo que le interesa, al verificar el valor del contexto del evento. El valor de contexto representa el nombre de la entrada (en realidad, la ruta de la entrada relativa a la ruta de acceso de su elemento principal, que es exactamente el nombre del elemento secundario). Usted tiene un ejemplo aquí .
Otras respuestas son correctas, debes mirar un directorio y filtrar tu archivo en particular. Sin embargo, es probable que desee un subproceso ejecutándose en segundo plano. La respuesta aceptada puede bloquearse indefinidamente en watchService.take();
y no cierra el WatchService. Una solución adecuada para un hilo separado podría verse así:
public class FileWatcher extends Thread {
private final File file;
private AtomicBoolean stop = new AtomicBoolean(false);
public FileWatcher(File file) {
this.file = file;
}
public boolean isStopped() { return stop.get(); }
public void stopThread() { stop.set(true); }
public void doOnChange() {
// Do whatever action you want here
}
@Override
public void run() {
try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
Path path = file.toPath().getParent();
path.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
while (!isStopped()) {
WatchKey key;
try { key = watcher.poll(25, TimeUnit.MILLISECONDS); }
catch (InterruptedException e) { return; }
if (key == null) { Thread.yield(); continue; }
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
@SuppressWarnings("unchecked")
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path filename = ev.context();
if (kind == StandardWatchEventKinds.OVERFLOW) {
Thread.yield();
continue;
} else if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY
&& filename.toString().equals(file.getName())) {
doOnChange();
}
boolean valid = key.reset();
if (!valid) { break; }
}
Thread.yield();
}
} catch (Throwable e) {
// Log or rethrow the error
}
}
}
Traté de trabajar desde la respuesta aceptada y este artículo . Debería poder usar este hilo con el new FileWatcher(new File("/home/me/myfile")).start()
y detenerlo al llamar a stopThread()
en el hilo.
Simplemente filtra los eventos para el archivo que deseas en el directorio:
final Path path = FileSystems.getDefault().getPath(System.getProperty("user.home"), "Desktop");
System.out.println(path);
try (final WatchService watchService = FileSystems.getDefault().newWatchService()) {
final WatchKey watchKey = path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
final WatchKey wk = watchService.take();
for (WatchEvent<?> event : wk.pollEvents()) {
//we only register "ENTRY_MODIFY" so the context is always a Path.
final Path changed = (Path) event.context();
System.out.println(changed);
if (changed.endsWith("myFile.txt")) {
System.out.println("My file has changed");
}
}
// reset the key
boolean valid = wk.reset();
if (!valid) {
System.out.println("Key has been unregisterede");
}
}
}
Aquí verificamos si el archivo modificado es "myFile.txt", si es entonces haga lo que sea.