Created
November 13, 2015 19:10
-
-
Save jvanzyl/294146fb97df3d23f5d0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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