StandardWatchEventKinds.ENTRY_MODIFY
のjavadocによると:
ディレクトリエントリが変更されました。このイベントに対してディレクトリが登録されると、ディレクトリ内のエントリが変更されたことが確認されると、WatchKeyがキューに入れられます。このイベントのイベント数は1以上です。
エディターを使用してファイルのコンテンツを編集すると、日付(またはその他のメタデータ)とコンテンツの両方が変更されます。したがって、2つのENTRY_MODIFY
イベントを取得しますが、それぞれのcount
は1になります(少なくともそれは私が見ているものです)。
次のコードを使用して手動で更新された(つまり、コマンドラインWatchService
を介して)構成ファイル(servers.cfg
に以前登録されたvi
)を監視しようとしています。
while(true) {
watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch(kind.name()) {
case "ENTRY_MODIFY":
handleModify(watchEvent.context()); // reload configuration class
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
}
}
watchKey.reset();
}
2つのENTRY_MODIFY
イベントを取得するので、上記は1回だけ必要なときに構成を2回リロードします。そのようなイベントが複数ある可能性があると仮定して、これらの1つを除いてすべてを無視する方法はありますか?
WatchService
APIにこのようなユーティリティがあれば、さらに良いでしょう。 (私は、各イベント間の時間を確認したくないのです。コード内のすべてのハンドラーメソッドは同期的です。
1つのディレクトリから監視対象ディレクトリにファイルを作成(コピー/貼り付け)した場合も、同じことが起こります。両方を1つのイベントに結合するにはどうすればよいですか?
同様の問題がありました-ディレクトリの同期を保つためにWatchService APIを使用していますが、多くの場合、更新が2回実行されていました。ファイルのタイムスタンプをチェックすることで問題を解決したようです-これは2番目のコピー操作を除外しているようです。 (少なくともWindows 7では-他のオペレーティングシステムで正しく動作するかどうかはわかりません)
似たようなものを使用できますか?ファイルからタイムスタンプを保存し、タイムスタンプが更新されたときにのみリロードしますか?
基になるファイルが2回更新されるため、WatcherServicesはイベントを2回報告します。コンテンツに対して1回、ファイルの変更時に1回。これらのイベントは短期間で発生します。これを解決するには、poll()
またはtake()
呼び出しとkey.pollEvents()
呼び出しの間でスリープします。例えば:
_@Override
@SuppressWarnings( "SleepWhileInLoop" )
public void run() {
setListening( true );
while( isListening() ) {
try {
final WatchKey key = getWatchService().take();
final Path path = get( key );
// Prevent receiving two separate ENTRY_MODIFY events: file modified
// and timestamp updated. Instead, receive one ENTRY_MODIFY event
// with two counts.
Thread.sleep( 50 );
for( final WatchEvent<?> event : key.pollEvents() ) {
final Path changed = path.resolve( (Path)event.context() );
if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) {
System.out.println( "Changed: " + changed );
}
}
if( !key.reset() ) {
ignore( path );
}
} catch( IOException | InterruptedException ex ) {
// Stop eavesdropping.
setListening( false );
}
}
}
_
sleep()
を呼び出すと、二重呼び出しを排除できます。遅延は3秒ほど長くする必要があります。
このような問題に対するgotoの解決策の1つは、一意のイベントリソースを単純にキューに入れ、許容可能な時間だけ処理を遅らせることです。この場合、到着する各イベントから派生したすべてのファイル名を含むSet<String>
を維持します。 Set<>
を使用すると、重複が追加されないため、1回だけ(遅延期間ごとに)処理されます。
興味深いイベントが到着するたびに、Set<>
にファイル名を追加し、遅延タイマーを再起動します。物事が落ち着き、遅延期間が経過すると、ファイルの処理に進みます。
AddFileToProcess()およびprocessFiles()メソッドは「同期」され、ConcurrentModificationExceptionsがスローされないようにします。
この単純化された/スタンドアロンの例は、Oracleの WatchDir.Java の派生です。
import static Java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static Java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static Java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static Java.nio.file.StandardWatchEventKinds.OVERFLOW;
import Java.io.IOException;
import Java.nio.file.FileSystems;
import Java.nio.file.FileVisitResult;
import Java.nio.file.Files;
import Java.nio.file.Path;
import Java.nio.file.Paths;
import Java.nio.file.SimpleFileVisitor;
import Java.nio.file.WatchEvent;
import Java.nio.file.WatchKey;
import Java.nio.file.WatchService;
import Java.nio.file.attribute.BasicFileAttributes;
import Java.util.HashMap;
import Java.util.HashSet;
import Java.util.Iterator;
import Java.util.Map;
import Java.util.Timer;
import Java.util.TimerTask;
public class DirectoryWatcherService implements Runnable {
@SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/*
* Wait this long after an event before processing the files.
*/
private final int DELAY = 500;
/*
* Use a SET to prevent duplicates from being added when multiple events on the
* same file arrive in quick succession.
*/
HashSet<String> filesToReload = new HashSet<String>();
/*
* Keep a map that will be used to resolve WatchKeys to the parent directory
* so that we can resolve the full path to an event file.
*/
private final Map<WatchKey,Path> keys;
Timer processDelayTimer = null;
private volatile Thread server;
private boolean trace = false;
private WatchService watcher = null;
public DirectoryWatcherService(Path dir, boolean recursive)
throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey,Path>();
if (recursive) {
registerAll(dir);
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
private synchronized void addFileToProcess(String filename) {
boolean alreadyAdded = filesToReload.add(filename) == false;
System.out.println("Queuing file for processing: "
+ filename + (alreadyAdded?"(already queued)":""));
if (processDelayTimer != null) {
processDelayTimer.cancel();
}
processDelayTimer = new Timer();
processDelayTimer.schedule(new TimerTask() {
@Override
public void run() {
processFiles();
}
}, DELAY);
}
private synchronized void processFiles() {
/*
* Iterate over the set of file to be processed
*/
for (Iterator<String> it = filesToReload.iterator(); it.hasNext();) {
String filename = it.next();
/*
* Sometimes you just have to do what you have to do...
*/
System.out.println("Processing file: " + filename);
/*
* Remove this file from the set.
*/
it.remove();
}
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException
{
if (dir.getFileName().toString().startsWith(".")) {
return FileVisitResult.SKIP_SUBTREE;
}
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
@SuppressWarnings("unchecked")
@Override
public void run() {
Thread thisThread = Thread.currentThread();
while (server == thisThread) {
try {
// wait for key to be signaled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
if (kind == ENTRY_MODIFY) {
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path name = ev.context();
Path child = dir.resolve(name);
String filename = child.toAbsolutePath().toString();
addFileToProcess(filename);
}
}
key.reset();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void start() {
server = new Thread(this);
server.setName("Directory Watcher Service");
server.start();
}
public void stop() {
Thread moribund = server;
server = null;
if (moribund != null) {
moribund.interrupt();
}
}
public static void main(String[] args) {
if (args==null || args.length == 0) {
System.err.println("You need to provide a path to watch!");
System.exit(-1);
}
Path p = Paths.get(args[0]);
if (!Files.isDirectory(p)) {
System.err.println(p + " is not a directory!");
System.exit(-1);
}
DirectoryWatcherService watcherService;
try {
watcherService = new DirectoryWatcherService(p, true);
watcherService.start();
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
以下は、複数のイベントの発生を避けるためにtimestamps
を使用した完全な実装です。
_import Java.io.File;
import Java.io.IOException;
import Java.nio.file.*;
import Java.nio.file.attribute.BasicFileAttributes;
import Java.util.HashMap;
import Java.util.Map;
import static Java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static Java.nio.file.StandardWatchEventKinds.*;
public abstract class DirectoryWatcher
{
private WatchService watcher;
private Map<WatchKey, Path> keys;
private Map<Path, Long> fileTimeStamps;
private boolean recursive;
private boolean trace = true;
@SuppressWarnings("unchecked")
private static <T> WatchEvent<T> cast(WatchEvent<?> event)
{
return (WatchEvent<T>) event;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path directory) throws IOException
{
WatchKey watchKey = directory.register(watcher, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);
addFileTimeStamps(directory);
if (trace)
{
Path existingFilePath = keys.get(watchKey);
if (existingFilePath == null)
{
System.out.format("register: %s\n", directory);
} else
{
if (!directory.equals(existingFilePath))
{
System.out.format("update: %s -> %s\n", existingFilePath, directory);
}
}
}
keys.put(watchKey, directory);
}
private void addFileTimeStamps(Path directory)
{
File[] files = directory.toFile().listFiles();
if (files != null)
{
for (File file : files)
{
if (file.isFile())
{
fileTimeStamps.put(file.toPath(), file.lastModified());
}
}
}
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(Path directory) throws IOException
{
Files.walkFileTree(directory, new SimpleFileVisitor<Path>()
{
@Override
public FileVisitResult preVisitDirectory(Path currentDirectory, BasicFileAttributes attrs)
throws IOException
{
register(currentDirectory);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Creates a WatchService and registers the given directory
*/
DirectoryWatcher(Path directory, boolean recursive) throws IOException
{
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<>();
fileTimeStamps = new HashMap<>();
this.recursive = recursive;
if (recursive)
{
System.out.format("Scanning %s ...\n", directory);
registerAll(directory);
System.out.println("Done.");
} else
{
register(directory);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Process all events for keys queued to the watcher
*/
void processEvents() throws InterruptedException, IOException
{
while (true)
{
WatchKey key = watcher.take();
Path dir = keys.get(key);
if (dir == null)
{
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event : key.pollEvents())
{
WatchEvent.Kind watchEventKind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (watchEventKind == OVERFLOW)
{
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> watchEvent = cast(event);
Path fileName = watchEvent.context();
Path filePath = dir.resolve(fileName);
long oldFileModifiedTimeStamp = fileTimeStamps.get(filePath);
long newFileModifiedTimeStamp = filePath.toFile().lastModified();
if (newFileModifiedTimeStamp > oldFileModifiedTimeStamp)
{
fileTimeStamps.remove(filePath);
onEventOccurred();
fileTimeStamps.put(filePath, filePath.toFile().lastModified());
}
if (recursive && watchEventKind == ENTRY_CREATE)
{
if (Files.isDirectory(filePath, NOFOLLOW_LINKS))
{
registerAll(filePath);
}
}
break;
}
boolean valid = key.reset();
if (!valid)
{
keys.remove(key);
if (keys.isEmpty())
{
break;
}
}
}
}
public abstract void onEventOccurred();
}
_
クラスを拡張し、onEventOccurred()
メソッドを実装します。
RxJavaを使用する場合は、throttleLast演算子を使用できます。以下の例では、監視対象ディレクトリ内の各ファイルについて、1000ミリ秒で最後のイベントのみが発行されます。
public class FileUtils {
private static final long EVENT_DELAY = 1000L;
public static Observable<FileWatchEvent> watch(Path directory, String glob) {
return Observable.<FileWatchEvent>create(subscriber -> {
final PathMatcher matcher = directory.getFileSystem().getPathMatcher("glob:" + glob);
WatchService watcher = FileSystems.getDefault().newWatchService();
subscriber.setCancellable(watcher::close);
try {
directory.register(watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY);
} catch (IOException e) {
subscriber.onError(e);
return;
}
while (!subscriber.isDisposed()) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException e) {
if (subscriber.isDisposed())
subscriber.onComplete();
else
subscriber.onError(e);
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind != OVERFLOW) {
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path child = directory.resolve(ev.context());
if (matcher.matches(child.getFileName()))
subscriber.onNext(new FileWatchEvent(kindToType(kind), child));
}
}
if (!key.reset()) {
subscriber.onError(new IOException("Invalid key"));
return;
}
}
}).groupBy(FileWatchEvent::getPath).flatMap(o -> o.throttleLast(EVENT_DELAY, TimeUnit.MILLISECONDS));
}
private static FileWatchEvent.Type kindToType(WatchEvent.Kind kind) {
if (StandardWatchEventKinds.ENTRY_CREATE.equals(kind))
return FileWatchEvent.Type.ADDED;
else if (StandardWatchEventKinds.ENTRY_MODIFY.equals(kind))
return FileWatchEvent.Type.MODIFIED;
else if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind))
return FileWatchEvent.Type.DELETED;
throw new RuntimeException("Invalid kind: " + kind);
}
public static class FileWatchEvent {
public enum Type {
ADDED, DELETED, MODIFIED
}
private Type type;
private Path path;
public FileWatchEvent(Type type, Path path) {
this.type = type;
this.path = path;
}
public Type getType() {
return type;
}
public Path getPath() {
return path;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileWatchEvent that = (FileWatchEvent) o;
if (type != that.type) return false;
return path != null ? path.equals(that.path) : that.path == null;
}
@Override
public int hashCode() {
int result = type != null ? type.hashCode() : 0;
result = 31 * result + (path != null ? path.hashCode() : 0);
return result;
}
}
}
WatchDir.Java を変更して、人為的な変更のみを受け取るようにしました。ファイルの.lastModified()
の比較。
long lastModi=0; //above for loop
if(kind==ENTRY_CREATE){
System.out.format("%s: %s\n", event.kind().name(), child);
}else if(kind==ENTRY_MODIFY){
if(child.toFile().lastModified() - lastModi > 1000){
System.out.format("%s: %s\n", event.kind().name(), child);
}
}else if(kind==ENTRY_DELETE){
System.out.format("%s: %s\n", event.kind().name(), child);
}
lastModi=child.toFile().lastModified();
Jdk7に問題がありますか?それは私にとって正しい結果を与えます(jdk7u15、windows)
コード
import Java.io.IOException;
import Java.nio.file.*;
public class WatchTest {
public void watchMyFiles() throws IOException, InterruptedException {
Path path = Paths.get("c:/temp");
WatchService watchService = path.getFileSystem().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: " +
watchEvent.count() + ", event: " + watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch (kind.name()) {
case "ENTRY_MODIFY":
handleModify(watchEvent.context()); // reload configuration class
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
default:
System.out.println("Event not expected " + event.kind().name());
}
}
watchKey.reset();
}
}
private void handleDelete(Path context) {
System.out.println("handleDelete " + context.getFileName());
}
private void handleModify(Path context) {
System.out.println("handleModify " + context.getFileName());
}
public static void main(String[] args) throws IOException, InterruptedException {
new WatchTest().watchMyFiles();
}
}
メモ帳を使用してファイルをコピーまたは編集すると、出力は次のようになります。
config.xml, count: 1, event: ENTRY_MODIFY
handleModify config.xml
Viは多くの追加ファイルを使用し、ファイル属性を複数回更新するようです。 notepad ++は正確に2回行います。
Oracleの WatchDir.Java と@nileshの提案をObservable
クラスにコンパイルし、監視対象ファイルが変更されたときにオブザーバーに通知します。
私はできるだけ読みやすく短くしようとしましたが、それでも100行以上で着陸しました。もちろん、改善は歓迎します。
使用法:
FileChangeNotifier fileReloader = new FileChangeNotifier(File file);
fileReloader.addObserver((Observable obj, Object arg) -> {
System.out.println("File changed for the " + arg + " time.");
});
GitHubでの私のソリューション:FileChangeNotifier.Java を参照してください。
デフォルトでfalseである「modifySolver」という名前のグローバルブール変数を定義することで、この問題を解決しました。以下に示すように、この問題に対処できます。
else if (eventKind.equals (ENTRY_MODIFY))
{
if (event.count() == 2)
{
getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
}
/*capture first modify event*/
else if ((event.count() == 1) && (!modifySolver))
{
getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
modifySolver = true;
}
/*discard the second modify event*/
else if ((event.count() == 1) && (modifySolver))
{
modifySolver = false;
}
}
Scalaを使用してbetter-files-akka
ライブラリ、受け入れられた回答で提案された解決策に基づいてこの回避策を考え出しました。
https://github.com/pathikrit/better-files/issues/31
trait ConfWatcher {
implicit def actorSystem: ActorSystem
private val confPath = "/home/codingkapoor/application.conf"
private val appConfFile = File(confPath)
private var appConfLastModified = appConfFile.lastModifiedTime
val watcher: ActorRef = appConfFile.newWatcher(recursive = false)
watcher ! on(EventType.ENTRY_MODIFY) { file =>
if (appConfLastModified.compareTo(file.lastModifiedTime) < 0) {
// TODO
appConfLastModified = file.lastModifiedTime
}
}
}
同様の問題がありました。私はこれが遅いことを知っていますが、それは誰かを助けるかもしれません。重複する_ENTRY_MODIFY
_を削除する必要がありました。 ENTRY_MODIFYがトリガーされるたびに、count()
は2または1を返します。1の場合、count()
1の別のイベントがあります。値を返し、カウンタが2になったときにのみ操作を実行します。次のようなことができます。
_WatchEvent event;
int count = 0;
if(event.count() == 2)
count = 2;
if(event.count() == 1)
count++;
if(count == 2){
//your operations here
count = 0;
}
_
私はこれを試しましたが、完全に機能しています:
import Java.io.IOException;
import Java.nio.file.*;
import Java.util.*;
import Java.util.concurrent.CopyOnWriteArrayList;
import Java.util.concurrent.atomic.AtomicBoolean;
import Java.util.concurrent.locks.Lock;
import Java.util.concurrent.locks.ReadWriteLock;
import Java.util.concurrent.locks.ReentrantReadWriteLock;
import static Java.nio.file.StandardWatchEventKinds.*;
public class FileWatcher implements Runnable, AutoCloseable {
private final WatchService service;
private final Map<Path, WatchTarget> watchTargets = new HashMap<>();
private final List<FileListener> fileListeners = new CopyOnWriteArrayList<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock r = lock.readLock();
private final Lock w = lock.writeLock();
private final AtomicBoolean running = new AtomicBoolean(false);
public FileWatcher() throws IOException {
service = FileSystems.getDefault().newWatchService();
}
@Override
public void run() {
if (running.compareAndSet(false, true)) {
while (running.get()) {
WatchKey key;
try {
key = service.take();
} catch (Throwable e) {
break;
}
if (key.isValid()) {
r.lock();
try {
key.pollEvents().stream()
.filter(e -> e.kind() != OVERFLOW)
.forEach(e -> watchTargets.values().stream()
.filter(t -> t.isInterested(e))
.forEach(t -> fireOnEvent(t.path, e.kind())));
} finally {
r.unlock();
}
if (!key.reset()) {
break;
}
}
}
running.set(false);
}
}
public boolean registerPath(Path path, boolean updateIfExists, WatchEvent.Kind... eventKinds) {
w.lock();
try {
WatchTarget target = watchTargets.get(path);
if (!updateIfExists && target != null) {
return false;
}
Path parent = path.getParent();
if (parent != null) {
if (target == null) {
watchTargets.put(path, new WatchTarget(path, eventKinds));
parent.register(service, eventKinds);
} else {
target.setEventKinds(eventKinds);
}
return true;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
w.unlock();
}
return false;
}
public void addFileListener(FileListener fileListener) {
fileListeners.add(fileListener);
}
public void removeFileListener(FileListener fileListener) {
fileListeners.remove(fileListener);
}
private void fireOnEvent(Path path, WatchEvent.Kind eventKind) {
for (FileListener fileListener : fileListeners) {
fileListener.onEvent(path, eventKind);
}
}
public boolean isRunning() {
return running.get();
}
@Override
public void close() throws IOException {
running.set(false);
w.lock();
try {
service.close();
} finally {
w.unlock();
}
}
private final class WatchTarget {
private final Path path;
private final Path fileName;
private final Set<String> eventNames = new HashSet<>();
private final Event lastEvent = new Event();
private WatchTarget(Path path, WatchEvent.Kind[] eventKinds) {
this.path = path;
this.fileName = path.getFileName();
setEventKinds(eventKinds);
}
private void setEventKinds(WatchEvent.Kind[] eventKinds) {
eventNames.clear();
for (WatchEvent.Kind k : eventKinds) {
eventNames.add(k.name());
}
}
private boolean isInterested(WatchEvent e) {
long now = System.currentTimeMillis();
String name = e.kind().name();
if (e.context().equals(fileName) && eventNames.contains(name)) {
if (lastEvent.name == null || !lastEvent.name.equals(name) || now - lastEvent.when > 100) {
lastEvent.name = name;
lastEvent.when = now;
return true;
}
}
return false;
}
@Override
public int hashCode() {
return path.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj == this || obj != null && obj instanceof WatchTarget && Objects.equals(path, ((WatchTarget) obj).path);
}
}
private final class Event {
private String name;
private long when;
}
public static void main(String[] args) throws IOException, InterruptedException {
FileWatcher watcher = new FileWatcher();
if (watcher.registerPath(Paths.get("filename"), false, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE)) {
watcher.addFileListener((path, eventKind) -> System.out.println(path + " -> " + eventKind.name()));
new Thread(watcher).start();
System.in.read();
}
watcher.close();
System.exit(0);
}
}
FileListener:
import Java.nio.file.Path;
import Java.nio.file.WatchEvent;
public interface FileListener {
void onEvent(Path path, WatchEvent.Kind eventKind);
}
/**
*
*
* in windows os, multiple event will be fired for a file create action
* this method will combine the event on same file
*
* for example:
*
* pathA -> createEvent -> createEvent
* pathA -> createEvent + modifyEvent, .... -> modifyEvent
* pathA -> createEvent + modifyEvent, ...., deleteEvent -> deleteEvent
*
*
*
* 在windows环境下创建一个文件会产生1个创建事件+多个修改事件, 这个方法用于合并重复事件
* 合并优先级为 删除 > 更新 > 创建
*
*
* @param events
* @return
*/
private List<WatchEvent<?>> filterEvent(List<WatchEvent<?>> events) {
// sorted by event create > modify > delete
Comparator<WatchEvent<?>> eventComparator = (eventA, eventB) -> {
HashMap<WatchEvent.Kind, Integer> map = new HashMap<>();
map.put(StandardWatchEventKinds.ENTRY_CREATE, 0);
map.put(StandardWatchEventKinds.ENTRY_MODIFY, 1);
map.put(StandardWatchEventKinds.ENTRY_DELETE, 2);
return map.get(eventA.kind()) - map.get(eventB.kind());
};
events.sort(eventComparator);
HashMap<String, WatchEvent<?>> hashMap = new HashMap<>();
for (WatchEvent<?> event : events) {
// if this is multiple event on same path
// the create event will added first
// then override by modify event
// then override by delete event
hashMap.put(event.context().toString(), event);
}
return new ArrayList<>(hashMap.values());
}
テストされていませんが、おそらくこれは動作します:
AtomicBoolean modifyEventFired = new AtomicBoolean();
modifyEventFired.set(false);
while(true) {
watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch(kind.name()) {
case "ENTRY_MODIFY":
if(!modifyEventFired.get()){
handleModify(watchEvent.context()); // reload configuration class
modifyEventFired.set(true);
}
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
}
}
modifyEventFired.set(false);
watchKey.reset();
}