Java 7 WatchService - Ignoring multiple occurrences of the same event

后端 未结 14 538
情歌与酒
情歌与酒 2020-12-05 02:21

The javadoc for StandardWatchEventKinds.ENTRY_MODIFY says:

Directory entry modified. When a directory is registered for this event the

相关标签:
14条回答
  • 2020-12-05 02:43

    WatcherServices reports events twice because the underlying file is updated twice. Once for the content and once for the file modified time. These events happen within a short time span. To solve this, sleep between the poll() or take() calls and the key.pollEvents() call. For example:

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

    Calling sleep() helps eliminate the double calls. The delay might have to be as high as three seconds.

    0 讨论(0)
  • 2020-12-05 02:43

    Here is a full implementation using timestamps to avoid firing multiple events:

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

    Extend the class and implement the onEventOccurred() method.

    0 讨论(0)
  • 2020-12-05 02:48

    One of my goto solutions for problems like this is to simply queue up the unique event resources and delay processing for an acceptable amount of time. In this case I maintain a Set<String> that contains every file name derived from each event that arrives. Using a Set<> ensures that duplicates don't get added and, therefore, will only be processed once (per delay period).

    Each time an interesting event arrives I add the file name to the Set<> and restart my delay timer. When things settle down and the delay period elapses, I proceed to processing the files.

    The addFileToProcess() and processFiles() methods are 'synchronized' to ensure that no ConcurrentModificationExceptions are thrown.

    This simplified/standalone example is a derivative of Oracle's 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());
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-12-05 02:48

    I modified WatchDir.java to receive only human-made modifications. Comparing .lastModified() of a file.

    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();
    
    0 讨论(0)
  • 2020-12-05 02:48

    I tried this and it's working perfectly :

    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 讨论(0)
  • 2020-12-05 02:50

    I solved this problem by defining a global boolean variable named "modifySolver" which be false by default. You can handle this problem as I show bellow:

    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 讨论(0)
提交回复
热议问题