package com.barbarysoftware.watchservice;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.*;
/**
* An implementation of the Watch Service API that uses polling. This is suitable for OS X pre-Leopard.
*
* @author Steve McLeod
*/
class MacOSXPollingWatchService extends AbstractWatchService {
private static final int INITIAL_DELAY = 10; // in seconds
private static final int DELAY = 10; // in seconds
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "WatchService Thread");
}
});
MacOSXPollingWatchService() {
}
@Override
WatchKey register(WatchableFile watchableFile, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifers) throws IOException {
final File file = watchableFile.getFile();
final Map<File, Long> lastModifiedMap = createLastModifiedMap(file);
final MacOSXWatchKey watchKey = new MacOSXWatchKey(this, events);
final FileTreeScanner scanner = new FileTreeScanner(watchKey, lastModifiedMap, file);
Runnable r = new Runnable() {
@Override
public void run() {
scanner.scan();
}
};
executor.scheduleWithFixedDelay(r, INITIAL_DELAY, DELAY, TimeUnit.SECONDS);
return watchKey;
}
private Map<File, Long> createLastModifiedMap(File file) {
Map<File, Long> lastModifiedMap = new ConcurrentHashMap<File, Long>();
for (File child : recursiveListFiles(file)) {
lastModifiedMap.put(child, child.lastModified());
}
return lastModifiedMap;
}
private static Set<File> recursiveListFiles(File file) {
Set<File> files = new HashSet<File>();
files.add(file);
if (file.isDirectory()) {
for (File child : file.listFiles()) {
files.addAll(recursiveListFiles(child));
}
}
return files;
}
@Override
void implClose() {
executor.shutdown();
}
private static class FileTreeScanner {
private final MacOSXWatchKey watchKey;
private final Map<File, Long> lastModifiedMap;
private final File folder;
private FileTreeScanner(MacOSXWatchKey watchKey, Map<File, Long> lastModifiedMap, File folder) {
this.watchKey = watchKey;
this.lastModifiedMap = lastModifiedMap;
this.folder = folder;
}
public void scan() {
scanFolderForChanges(folder);
}
private void scanFolderForChanges(File folder) {
final Set<File> filesOnDisk = recursiveListFiles(folder);
for (File file : findCreatedFiles(filesOnDisk)) {
if (watchKey.isReportCreateEvents()) {
watchKey.signalEvent(StandardWatchEventKind.ENTRY_CREATE, file);
}
lastModifiedMap.put(file, file.lastModified());
}
for (File file : findModifiedFiles(filesOnDisk)) {
if (watchKey.isReportModifyEvents()) {
watchKey.signalEvent(StandardWatchEventKind.ENTRY_MODIFY, file);
}
lastModifiedMap.put(file, file.lastModified());
}
for (File file : findDeletedFiles(filesOnDisk)) {
if (watchKey.isReportDeleteEvents()) {
watchKey.signalEvent(StandardWatchEventKind.ENTRY_DELETE, file);
}
lastModifiedMap.remove(file);
}
}
private List<File> findModifiedFiles(Set<File> filesOnDisk) {
List<File> modifiedFileList = new ArrayList<File>();
for (File file : filesOnDisk) {
final Long lastModified = lastModifiedMap.get(file);
if (lastModified != null && lastModified != file.lastModified()) {
modifiedFileList.add(file);
}
}
return modifiedFileList;
}
private List<File> findCreatedFiles(Set<File> filesOnDisk) {
List<File> createdFileList = new ArrayList<File>();
for (File file : filesOnDisk) {
if (!lastModifiedMap.containsKey(file)) {
createdFileList.add(file);
}
}
return createdFileList;
}
private List<File> findDeletedFiles(Set<File> filesOnDisk) {
List<File> deletedFileList = new ArrayList<File>();
for (File file : lastModifiedMap.keySet()) {
if (!filesOnDisk.contains(file)) {
deletedFileList.add(file);
}
}
return deletedFileList;
}
}
}