Skip to content

Instantly share code, notes, and snippets.

@jvanzyl
Created November 13, 2015 19:10
Show Gist options
  • Save jvanzyl/294146fb97df3d23f5d0 to your computer and use it in GitHub Desktop.
Save jvanzyl/294146fb97df3d23f5d0 to your computer and use it in GitHub Desktop.
package com.barbarysoftware.watchservice;
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 static org.junit.Assert.assertEquals;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.Watchable;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.codehaus.plexus.util.FileUtils;
import org.junit.Test;
import com.barbarysoftware.watchservice.FileSystem.FileSystemAction;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
public class MacOSListeningWatchServiceTest {
@Test
public void validateMacOSListeningWatchService() throws Exception {
//
// 1. Start our service
// 2. Play our filesystem actions
// 3. Stop when all our events have been drained and processed
// 4. Validate that the events emitted are consistent with the filesystem actions played
//
File directory = new File(new File("").getAbsolutePath(), "target/directory");
FileUtils.deleteDirectory(directory);
directory.mkdirs();
WatchService watcher = new MacOSXListeningWatchService();
Watchable directoryToWatch = new WatchablePath(directory.toPath());
directoryToWatch.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
FileSystem fileSystem = new FileSystem(directory, 100) //
.create("one.txt") //
.create("two.txt") //
.create("three.txt") //
.wait(200) //
.update("three.txt", " 1") //
.wait(200) //
.update("three.txt", " 2") //
.delete("one.txt");
// Collect our filesystem actions
List<FileSystemAction> actions = fileSystem.actions();
ExecutorService executor = Executors.newSingleThreadExecutor();
// Fire up the filesystem watcher
Future<ListMultimap<Path, WatchEvent<Path>>> future = executor.submit(watcher(watcher, actions));
// Play our filesystem events
fileSystem.playActions();
// Wait for the future to complete which is when the right number of events are captured
ListMultimap<Path, WatchEvent<Path>> events = future.get(10, TimeUnit.SECONDS);
// Close down the filesystem watcher
watcher.close();
// Let's see if everything works!
assertEquals(actions.size(), events.size());
//
// Now we make a map of the events keyed by the path. The order in which we
// play the filesystem actions is not necessarily the order in which the events are
// emitted. In the test above I often see the create file event for three.txt before
// two.txt. We just want to make sure that the action for a particular path agrees
// with the corresponding event for that file. For a given path we definitely want
// the order of the played actions to match the order of the events emitted.
//
List<WatchEvent<Path>> one = events.get(path(directory, "one.txt"));
assertEquals(one.get(0).kind(), actions.get(0).kind);
List<WatchEvent<Path>> two = events.get(path(directory, "two.txt"));
assertEquals(two.get(0).kind(), actions.get(1).kind);
List<WatchEvent<Path>> three = events.get(path(directory, "three.txt"));
assertEquals(three.get(0).kind(), actions.get(2).kind);
assertEquals(three.get(1).kind(), actions.get(3).kind);
assertEquals(three.get(2).kind(), actions.get(4).kind);
}
private Path path(File directory, String path) {
return directory.toPath().resolve(Paths.get(path));
}
private static Callable<ListMultimap<Path, WatchEvent<Path>>> watcher(final WatchService watcher, final List<FileSystemAction> actions) {
return new Callable<ListMultimap<Path, WatchEvent<Path>>>() {
public ListMultimap<Path, WatchEvent<Path>> call() {
ListMultimap<Path, WatchEvent<Path>> events = ArrayListMultimap.create();
int actionsProcessed = 0;
int totalActions = actions.size();
for (;;) {
// wait for key to be signaled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
System.out.println("We were interrupted!");
return events;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
// The filename is the context of the event.
@SuppressWarnings({"unchecked"})
WatchEvent<Path> ev = (WatchEvent<Path>) event;
System.out.printf("Detected file system event: %s at %s%n", kind, ev.context());
// Igore entries on directories for our test, we only care about files.
if (!ev.context().toFile().isDirectory()) {
events.put(ev.context(), ev);
actionsProcessed++;
}
System.out.println(actionsProcessed + "/" + totalActions + " actions processed.");
if (actionsProcessed == totalActions) {
return events;
}
}
// Reset the key -- this step is critical to receive further watch events.
boolean valid = key.reset();
if (!valid) {
break;
}
}
return events;
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment