web-dev-qa-db-fra.com

Java 7 WatchService - Ignorer plusieurs occurrences du même événement

Le javadoc pour StandardWatchEventKinds.ENTRY_MODIFY dit:

Entrée de répertoire modifiée. Lorsqu'un répertoire est enregistré pour cela événement, la WatchKey est mise en file d'attente lorsqu'il est constaté qu'une entrée est en le répertoire a été modifié. Le nombre d'événements pour cet événement est 1 ou plus grand.

Lorsque vous modifiez le contenu d'un fichier à l'aide d'un éditeur, la date (ou d'autres métadonnées) et le contenu sont modifiés. Vous obtenez donc deux événements ENTRY_MODIFY, mais chacun aura une count de 1 (du moins, c'est ce que je vois).

J'essaie de surveiller un fichier de configuration (servers.cfg précédemment enregistré auprès de la WatchService) qui est mis à jour manuellement (c'est-à-dire via la ligne de commande vi) avec le code suivant:

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();       
}

Étant donné que vous obtenez deux événements ENTRY_MODIFY, les opérations ci-dessus rechargeraient la configuration deux fois lorsqu'une seule fois est nécessaire. Existe-t-il un moyen d'ignorer tous ces éléments sauf un, en supposant qu'il puisse y avoir plus d'un événement de ce type? 

Si l'API WatchService a un tel utilitaire, tant mieux. (Je ne veux en quelque sorte pas vérifier les heures entre chaque événement. Toutes les méthodes de gestionnaire de mon code sont synchrones.

La même chose se produit si vous créez (copiez/collez) un fichier d'un répertoire au répertoire surveillé. Comment pouvez-vous combiner les deux en un seul événement?

32

J'ai eu un problème similaire - j'utilise l'API WatchService pour maintenir les répertoires synchronisés, mais j'ai constaté que dans de nombreux cas, les mises à jour étaient effectuées deux fois. Je semble avoir résolu le problème en vérifiant l'horodatage des fichiers - cela semble masquer la deuxième opération de copie. (Au moins dans Windows 7 - je ne peux pas être sûr que cela fonctionnera correctement dans d'autres systèmes d'exploitation)

Peut-être que vous pourriez utiliser quelque chose de similaire? Stocker l'horodatage à partir du fichier et recharger uniquement lorsque l'horodatage est mis à jour?

16
tofarr

WatcherServices signale les événements deux fois parce que le fichier sous-jacent est mis à jour deux fois. Une fois pour le contenu et une fois pour le fichier modifié. Ces événements se produisent dans un court laps de temps. Pour résoudre ce problème, dormez entre l'appel poll() ou take() et l'appel key.pollEvents(). Par exemple:

@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 );
    }
  }
}

Appeler sleep() aide à éliminer les doubles appels. Le délai peut devoir atteindre trois secondes.

31
user262129

Une de mes solutions pour de tels problèmes consiste à mettre simplement en file d'attente les ressources d'événements uniques et à différer le traitement pendant un laps de temps raisonnable. Dans ce cas, je maintiens un Set<String> qui contient chaque nom de fichier dérivé de chaque événement qui arrive. L'utilisation d'un Set<> garantit que les doublons ne sont pas ajoutés et, par conséquent, ne seront traités qu'une seule fois (par délai).

Chaque fois qu'un événement intéressant arrive, j'ajoute le nom du fichier au Set<> et relance le temporisateur. Lorsque les choses se sont arrangées et que le délai s'est écoulé, je procède au traitement des fichiers.

Les méthodes addFileToProcess () et processFiles () sont "synchronisées" pour garantir qu'aucune exception ConcurrentModificationException n'est levée.

Cet exemple simplifié/autonome est un dérivé de WatchDir.Java d'Oracle:

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());
        }
    }

}
4
Kevin

Voici une implémentation complète utilisant timestamps pour éviter de déclencher plusieurs événements:

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();
}

Étendez la classe et implémentez la méthode onEventOccurred().

3
BullyWiiPlaza

Si vous utilisez RxJava, vous pouvez utiliser l'opérateur throttleLast. Dans l'exemple ci-dessous, seul le dernier événement sur 1000 millisecondes est émis pour chaque fichier du répertoire surveillé.

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;
        }
    }
}
2
user2252051

J'ai modifié WatchDir.Java pour ne recevoir que les modifications apportées par l'homme. Comparer .lastModified() d'un fichier.

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();
2
Nilesh

Êtes-vous sûr qu'il y a un problème avec jdk7? Cela donne un résultat correct pour moi (jdk7u15, windows)

Code

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();
    }
}

La sortie est comme ci-dessous - lorsque le fichier est copié ou modifié à l'aide du bloc-notes 

config.xml, count: 1, event: ENTRY_MODIFY
handleModify config.xml

Vi utilise de nombreux fichiers supplémentaires et semble mettre à jour l'attribut de fichier plusieurs fois. notepad ++ fait exactement deux fois.

2
Jayan

J'ai compilé les suggestions WatchDir.Java et @ nilesh d'Oracle dans une classe Observable qui avertira ses observateurs une fois que le fichier surveillé aura été modifié.

J'ai essayé de le rendre aussi lisible et bref que possible, mais j'ai quand même atterri avec plus de 100 lignes. Les améliorations sont les bienvenues, bien sûr.

Usage:

FileChangeNotifier fileReloader = new FileChangeNotifier(File file);
fileReloader.addObserver((Observable obj, Object arg) -> {
    System.out.println("File changed for the " + arg + " time.");
});

Voir ma solution sur GitHub: FileChangeNotifier.Java .

1
Florian Sesser

J'ai eu le même problème. Je sais que cela est en retard mais que cela pourrait aider quelqu'un… __. Je devais simplement éliminer les doublons ENTRY_MODIFY. Chaque fois que ENTRY_MODIFY est déclenché, count() renvoie 2 ou 1. Si c'est 1, il y aura un autre événement avec count() 1 . Il suffit donc de mettre un compteur global qui conserve le nombre de valeurs de retour et effectue les opérations uniquement lorsque le compteur devient 2. Ce que vous pouvez faire:

WatchEvent event; 
int count = 0;

if(event.count() == 2)
     count = 2;

if(event.count() == 1)
     count++;

if(count == 2){
     //your operations here
     count = 0;
}
0
user2729516

J'ai essayé ça et ça marche parfaitement:

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);

}
0
FaNaJ
    /**
 * 
 * 
 * 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());


}
0
宏杰李

Non testé, mais peut-être que cela fonctionnera: 

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();       
}
0
Robert H

J'ai résolu ce problème en définissant une variable booléenne globale appelée "modifySolver" qui est fausse par défaut. Vous pouvez gérer ce problème comme je le montre ci-dessous:

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;
            }
        }
0
khesam109