001package com.dev9.mvnwatcher.event;
002
003import com.google.common.annotations.VisibleForTesting;
004import com.google.common.eventbus.EventBus;
005
006import java.io.IOException;
007import java.nio.file.*;
008import java.nio.file.attribute.BasicFileAttributes;
009import java.util.List;
010import java.util.Objects;
011import java.util.concurrent.Callable;
012import java.util.concurrent.ExecutionException;
013import java.util.concurrent.FutureTask;
014import java.util.concurrent.TimeUnit;
015
016import static java.nio.file.StandardWatchEventKinds.*;
017
018/**
019 * Based on code written originally by bbejeck
020 */
021
022public class DirectoryEventWatcherImpl implements DirectoryEventWatcher {
023
024    private FutureTask<Integer> watchTask;
025    private EventBus eventBus;
026    private WatchService watchService;
027    private volatile boolean keepWatching = true;
028
029    public DirectoryEventWatcherImpl(EventBus eventBus) throws IOException {
030        this.eventBus = Objects.requireNonNull(eventBus);
031        if (watchService == null) {
032            watchService = FileSystems.getDefault().newWatchService();
033        }
034    }
035
036    @Override
037    public void start() throws IOException {
038        createWatchTask();
039        startWatching();
040    }
041
042    public void add(Path p) throws IOException {
043        Objects.requireNonNull(p);
044        if (!p.toFile().exists())
045            throw new IllegalArgumentException("Path " + p.toFile().getAbsolutePath() + " does not actually exist.");
046
047        Files.walkFileTree(p, new WatchServiceRegisteringVisitor());
048    }
049
050    @Override
051    public boolean isRunning() {
052        return watchTask != null && !watchTask.isDone();
053    }
054
055    @Override
056    public void stop() {
057        keepWatching = false;
058    }
059
060    @VisibleForTesting
061    public Integer getEventCount() {
062        try {
063            return watchTask.get();
064        } catch (InterruptedException | ExecutionException e) {
065            throw new RuntimeException(e);
066        }
067    }
068
069
070    private void createWatchTask() {
071        watchTask = new FutureTask<>(new Callable<Integer>() {
072            private int totalEventCount;
073
074            @Override
075            public Integer call() throws Exception {
076
077                // Loop to keep watching until shutdown
078                while (keepWatching) {
079                    WatchKey watchKey = watchService.poll(500, TimeUnit.MILLISECONDS);
080                    if (watchKey != null) {
081                        List<WatchEvent<?>> events = watchKey.pollEvents();
082                        Path watched = (Path) watchKey.watchable();
083                        PathEvents pathEvents = new PathEvents(watchKey.isValid(), watched);
084                        for (WatchEvent event : events) {
085                            pathEvents.add(new PathEvent((Path) event.context(), event.kind()));
086                            totalEventCount++;
087                        }
088                        watchKey.reset();
089                        eventBus.post(pathEvents);
090                    }
091                }
092
093                // Fell out of the watch loop, shut down the service.
094                watchService.close();
095
096                return totalEventCount;
097            }
098        });
099    }
100
101    private void startWatching() {
102        new Thread(watchTask).start();
103    }
104
105    private class WatchServiceRegisteringVisitor extends SimpleFileVisitor<Path> {
106        @Override
107        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
108            Objects.requireNonNull(dir);
109            Objects.requireNonNull(watchService);
110
111            dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
112            return FileVisitResult.CONTINUE;
113        }
114    }
115}