FileWatcher refactoring
authorMaxim Shafirov <max@jetbrains.com>
Wed, 24 Sep 2008 17:31:14 +0000 (21:31 +0400)
committerMaxim Shafirov <max@jetbrains.com>
Wed, 24 Sep 2008 17:31:14 +0000 (21:31 +0400)
bin/fsnotifier [new file with mode: 0644]
platform-api/src/com/intellij/openapi/vfs/watcher/ChangeKind.java [new file with mode: 0644]
platform-impl/src/com/intellij/idea/CommandLineApplication.java
platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java [new file with mode: 0644]
platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java
platform-resources/src/componentSets/Platform.xml
tools/fsNotifier/mac/fsnotifier.c [new file with mode: 0644]
tools/fsNotifier/mac/make.sh [new file with mode: 0644]

diff --git a/bin/fsnotifier b/bin/fsnotifier
new file mode 100644 (file)
index 0000000..9961c91
Binary files /dev/null and b/bin/fsnotifier differ
diff --git a/platform-api/src/com/intellij/openapi/vfs/watcher/ChangeKind.java b/platform-api/src/com/intellij/openapi/vfs/watcher/ChangeKind.java
new file mode 100644 (file)
index 0000000..37bad93
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * @author max
+ */
+package com.intellij.openapi.vfs.watcher;
+
+public enum ChangeKind {
+  CREATE,
+  DELETE,
+  STATS,
+  CHANGE,
+  DIRTY,
+  RECDIRTY,
+  RESET
+}
\ No newline at end of file
index d39122bc54984d4258815c27863f302072c648bd..00d350b7ec73c905500a4da67e1197488f004e41 100644 (file)
@@ -4,7 +4,7 @@ import com.intellij.ide.impl.DataManagerImpl;
 import com.intellij.openapi.actionSystem.DataContext;
 import com.intellij.openapi.application.ex.ApplicationManagerEx;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.vfs.local.win32.FileWatcher;
+import com.intellij.openapi.vfs.impl.local.FileWatcher;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 
diff --git a/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java b/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java
new file mode 100644 (file)
index 0000000..d5eb91c
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * @author max
+ */
+package com.intellij.openapi.vfs.impl.local;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.newvfs.ManagingFS;
+import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
+import com.intellij.openapi.vfs.watcher.ChangeKind;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class FileWatcher {
+  @NonNls public static final String PROPERTY_WATCHER_DISABLED = "filewatcher.disabled";
+
+  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.FileWatcher");
+
+  @NonNls private static final String GIVEUP_COMMAND = "GIVEUP";
+  @NonNls private static final String RESET_COMMAND = "RESET";
+  @NonNls private static final String UNWATCHEABLE_COMMAND = "UNWATCHEABLE";
+  @NonNls private static final String ROOTS_COMMAND = "ROOTS";
+  @NonNls private static final String EXIT_COMMAND = "EXIT";
+
+  private final Object LOCK = new Object();
+  private List<String> myDirtyPaths = new ArrayList<String>();
+  private List<String> myDirtyRecursivePaths = new ArrayList<String>();
+  private List<String> myDirtyDirs = new ArrayList<String>();
+  private List<String> myManualWatchRoots = new ArrayList<String>();
+
+  private List<String> myRecursiveWatchRoots = new ArrayList<String>();
+  private List<String> myFlatWatchRoots = new ArrayList<String>();
+
+  private Process notifierProcess;
+  private BufferedReader notifierReader;
+  private BufferedWriter notifierWriter;
+
+  private static FileWatcher ourInstance = new FileWatcher();
+  private int attemptCount = 0;
+  private static final int MAX_PROCESS_LAUNCH_ATTEMPT_COUNT = 10;
+  private boolean isShuttingDown = false;
+
+  public static FileWatcher getInstance() {
+    return ourInstance;
+  }
+
+  private FileWatcher() {
+    try {
+      if (!"true".equals(System.getProperty(PROPERTY_WATCHER_DISABLED))) {
+        startupProcess();
+      }
+    }
+    catch (IOException e) {
+      // Ignore
+    }
+
+    if (notifierProcess != null) {
+      LOG.info("Native file watcher is operational.");
+      new WatchForChangesThread().start();
+
+      Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+        public void run() {
+          try {
+            isShuttingDown = true;
+            shutdownProcess();
+          }
+          catch (IOException e) {
+            // Do nothing.
+          }
+        }
+      }));
+    }
+    else {
+      LOG.info("Native file watcher failed to startup.");
+    }
+  }
+
+  public List<String> getDirtyPaths() {
+    synchronized (LOCK) {
+      final List<String> result = myDirtyPaths;
+      myDirtyPaths = new ArrayList<String>();
+      return result;
+    }
+  }
+
+  public List<String> getDirtyRecursivePaths() {
+    synchronized (LOCK) {
+      final List<String> result = myDirtyRecursivePaths;
+      myDirtyRecursivePaths = new ArrayList<String>();
+      return result;
+    }
+
+  }
+
+  public List<String> getDirtyDirs() {
+    synchronized (LOCK) {
+      final List<String> result = myDirtyDirs;
+      myDirtyDirs = new ArrayList<String>();
+      return result;
+    }
+  }
+
+  public List<String> getManualWatchRoots() {
+    synchronized (LOCK) {
+      return Collections.unmodifiableList(myManualWatchRoots);
+    }
+  }
+
+  public void setWatchRoots(List<String> recursive, List<String> flat) {
+    synchronized (LOCK) {
+      try {
+        if (myRecursiveWatchRoots.equals(recursive) && myFlatWatchRoots.equals(flat)) return;
+
+        myRecursiveWatchRoots = recursive;
+        myFlatWatchRoots = flat;
+
+        writeLine(ROOTS_COMMAND);
+        for (String path : recursive) {
+          writeLine(path);
+        }
+        for (String path : flat) {
+          writeLine("|" + path);
+        }
+        writeLine("#");
+      }
+      catch (IOException e) {
+        LOG.error(e);
+      }
+    }
+  }
+
+  private void setManualWatchRoots(List<String> roots) {
+    synchronized (LOCK) {
+      myManualWatchRoots = roots;
+    }
+  }
+
+  private void startupProcess() throws IOException {
+    if (isShuttingDown) return;
+
+    if (attemptCount++ > MAX_PROCESS_LAUNCH_ATTEMPT_COUNT) {
+      throw new IOException("Can't launch process anymore");
+    }
+
+    shutdownProcess();
+
+    notifierProcess = Runtime.getRuntime().exec(new String[]{PathManager.getBinPath() + "/fsnotifier"});
+    notifierReader = new BufferedReader(new InputStreamReader(notifierProcess.getInputStream()));
+    notifierWriter = new BufferedWriter(new OutputStreamWriter(notifierProcess.getOutputStream()));
+  }
+
+  private void shutdownProcess() throws IOException {
+    if (notifierProcess != null) {
+      writeLine(EXIT_COMMAND);
+
+      notifierProcess = null;
+      notifierReader = null;
+      notifierWriter = null;
+    }
+  }
+
+  public boolean isOperational() {
+    return notifierProcess != null;
+  }
+
+  private class WatchForChangesThread extends Thread {
+
+    public WatchForChangesThread() {
+      //noinspection HardCodedStringLiteral
+      super("WatchForChangesThread");
+    }
+
+    public void run() {
+      try {
+        while (true) {
+          if (ApplicationManager.getApplication().isDisposeInProgress() || notifierProcess == null || isShuttingDown) return;
+
+          final String command = readLine();
+          if (command == null) {
+            // Unexpected process exit, relaunch attempt
+            startupProcess();
+            continue;
+          }
+
+          if (GIVEUP_COMMAND.equals(command)) {
+            shutdownProcess();
+            return;
+          }
+          if (RESET_COMMAND.equals(command)) {
+            reset();
+          }
+          else if (UNWATCHEABLE_COMMAND.equals(command)) {
+            List<String> roots = new ArrayList<String>();
+            do {
+              final String path = readLine();
+              if (path == null || "#".equals(path)) break;
+              roots.add(path);
+            }
+            while (true);
+
+            setManualWatchRoots(roots);
+          }
+          else {
+            String path = readLine();
+            if (path == null) {
+              // Unexpected process exit, relaunch attempt
+              startupProcess();
+              continue;
+            }
+
+            if (isWatcheable(path)) {
+              try {
+                onPathChange(ChangeKind.valueOf(command), path);
+              }
+              catch (IllegalArgumentException e) {
+                LOG.error("Illegal watcher command: " + command);
+              }
+            }
+          }
+        }
+      }
+      catch (IOException e) {
+        LOG.info("Watcher terminated and attempt to restart has failed. Exiting watching thread.", e);
+      }
+    }
+  }
+
+  private void writeLine(String line) throws IOException {
+    notifierWriter.write(line);
+    notifierWriter.newLine();
+    notifierWriter.flush();
+  }
+
+  private String readLine() throws IOException {
+    return notifierReader.readLine();
+  }
+
+  private boolean isWatcheable(final String path) {
+    if (path == null) return false;
+
+    synchronized (LOCK) {
+      for (String root : myRecursiveWatchRoots) {
+        if (FileUtil.startsWith(path, root)) return true;
+      }
+
+      for (String root : myFlatWatchRoots) {
+        if (FileUtil.pathsEqual(path, root)) return true;
+        if (FileUtil.pathsEqual(new File(path).getParentFile().getPath(), root)) return true;
+      }
+    }
+
+    return false;
+  }
+
+  private void onPathChange(final ChangeKind changeKind, final String path) {
+    synchronized (LOCK) {
+      switch (changeKind) {
+        case STATS:
+        case CHANGE:
+          myDirtyPaths.add(path);
+          break;
+
+        case CREATE:
+        case DELETE:
+          myDirtyPaths.add(new File(path).getParentFile().getPath());
+          break;
+
+        case DIRTY:
+          myDirtyDirs.add(path);
+          break;
+
+        case RECDIRTY:
+          myDirtyRecursivePaths.add(path);
+          break;
+
+        case RESET:
+          reset();
+          break;
+      }
+    }
+  }
+
+  private void reset() {
+    synchronized (LOCK) {
+      myDirtyPaths.clear();
+      myDirtyDirs.clear();
+      myDirtyRecursivePaths.clear();
+
+      for (VirtualFile root : ManagingFS.getInstance().getLocalRoots()) {
+        ((NewVirtualFile)root).markDirtyRecursively();
+      }
+    }
+  }
+}
\ No newline at end of file
index a513ca612a91fe227698a9544732431ae6ed90bc..ec2ac11fcb39bf10f7ed4dbbf30c837720bae9fe 100644 (file)
@@ -22,7 +22,6 @@ import com.intellij.util.concurrency.JBReentrantReadWriteLock;
 import com.intellij.util.concurrency.LockFactory;
 import com.intellij.util.containers.HashSet;
 import com.intellij.util.io.fs.IFile;
-import com.intellij.vfs.local.win32.FileWatcher;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -41,11 +40,8 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
   private final List<WatchRequest> myRootsToWatch = new ArrayList<WatchRequest>();
   private WatchRequest[] myCachedNormalizedRequests = null;
 
-  private final Set<String> myDirtyFiles = new HashSet<String>(); // dirty files when FileWatcher is available
-  private final Set<String> myDeletedFiles = new HashSet<String>();
-
   private final List<LocalFileOperationsHandler> myHandlers = new ArrayList<LocalFileOperationsHandler>();
-  private List<String> myManualWatchRoots = new ArrayList<String>();
+  private final FileWatcher myWatcher;
 
   private static class WatchRequestImpl implements WatchRequest {
     public final String myRootPath;
@@ -109,9 +105,8 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
   }
 
   public LocalFileSystemImpl() {
-    if (FileWatcher.isAvailable()) {
-      FileWatcher.initialize();
-      new WatchForChangesThread().start();
+    myWatcher = FileWatcher.getInstance();
+    if (myWatcher.isOperational()) {
       new StoreRefreshStatusThread().start();
     }
   }
@@ -141,20 +136,12 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
     }
 
     myRootsToWatch.clear();
-    myDirtyFiles.clear();
-    myDeletedFiles.clear();
 
     final File file = new File(FileUtil.getTempDirectory());
     String path = file.getCanonicalPath().replace(File.separatorChar, '/');
     addRootToWatch(path, true);
   }
 
-  private static void updateFileWatcher() {
-    if (FileWatcher.isAvailable()) {
-      FileWatcher.interruptWatcher();
-    }
-  }
-
   public String getProtocol() {
     return PROTOCOL;
   }
@@ -291,26 +278,44 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
   }
 
   private void storeRefreshStatusToFiles() {
-    if (FileWatcher.isAvailable()) {
-      markPathsDirty(getAndClear(myDirtyFiles));
-      markPathsDirty(getAndClear(myDeletedFiles));
+    if (FileWatcher.getInstance().isOperational()) {
+      // TODO: different ways to marky dirty for all these cases
+      markPathsDirty(FileWatcher.getInstance().getDirtyPaths());
+      markFlatDirsDirty(FileWatcher.getInstance().getDirtyDirs());
+      markRecursiveDirsDirty(FileWatcher.getInstance().getDirtyRecursivePaths());
     }
   }
 
-  private static String[] getAndClear(final Set<String> set) {
-    synchronized (set) {
-      final String[] copy = set.toArray(new String[set.size()]);
-      set.clear();
-      return copy;
+  private void markPathsDirty(final List<String> dirtyFiles) {
+    for (String dirtyFile : dirtyFiles) {
+      String path = dirtyFile.replace(File.separatorChar, '/');
+      VirtualFile file = findFileByPathIfCached(path);
+      if (file instanceof NewVirtualFile) {
+        ((NewVirtualFile)file).markDirty();
+      }
     }
   }
 
-  private void markPathsDirty(final String[] dirtyFiles) {
+  private void markFlatDirsDirty(final List<String> dirtyFiles) {
     for (String dirtyFile : dirtyFiles) {
       String path = dirtyFile.replace(File.separatorChar, '/');
       VirtualFile file = findFileByPathIfCached(path);
       if (file instanceof NewVirtualFile) {
-        ((NewVirtualFile)file).markDirty();
+        final NewVirtualFile nvf = (NewVirtualFile)file;
+        nvf.markDirty();
+        for (VirtualFile child : nvf.getCachedChildren()) {
+          ((NewVirtualFile)child).markDirty();
+        }
+      }
+    }
+  }
+
+  private void markRecursiveDirsDirty(final List<String> dirtyFiles) {
+    for (String dirtyFile : dirtyFiles) {
+      String path = dirtyFile.replace(File.separatorChar, '/');
+      VirtualFile file = findFileByPathIfCached(path);
+      if (file instanceof NewVirtualFile) {
+        ((NewVirtualFile)file).markDirtyRecursively();
       }
     }
   }
@@ -318,8 +323,8 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
   public void markSuspicousFilesDirty(List<VirtualFile> files) {
     storeRefreshStatusToFiles();
 
-    if (FileWatcher.isAvailable()) {
-      for (String root : myManualWatchRoots) {
+    if (myWatcher.isOperational()) {
+      for (String root : myWatcher.getManualWatchRoots()) {
         final VirtualFile suspicousRoot = findFileByPathIfCached(root);
         if (suspicousRoot != null) {
           ((NewVirtualFile)suspicousRoot).markDirtyRecursively();
@@ -371,84 +376,30 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
     return path.replace(File.separatorChar, '/');
   }
 
-  private class WatchForChangesThread extends Thread {
-    public WatchForChangesThread() {
-      //noinspection HardCodedStringLiteral
-      super("WatchForChangesThread");
-    }
-
-    public void run() {
-      updateFileWatcher();
-      try {
-        while (true) {
-          if (ApplicationManager.getApplication().isDisposeInProgress()) return;
-
-          FileWatcher.ChangeInfo[] infos = FileWatcher.waitForChange();
-
-          if (infos == null) {
-            setUpFileWatcher();
-          }
-          else {
-            for (FileWatcher.ChangeInfo info : infos) {
-              if (info == null) continue;
-
-              String path = info.getFilePath();
-              int changeType = info.getChangeType();
-              if (changeType == FileWatcher.FILE_MODIFIED) {
-                synchronized (myDirtyFiles) {
-                  myDirtyFiles.add(path);
-                }
-              }
-              else if (changeType == FileWatcher.FILE_ADDED || changeType == FileWatcher.FILE_RENAMED_NEW_NAME) {
-                synchronized (myDirtyFiles) {
-                  String parent = new File(path).getParent();
-                  if (parent != null) {
-                    myDirtyFiles.add(parent);
-                  }
-                }
-              }
-              else if (changeType == FileWatcher.FILE_REMOVED || changeType == FileWatcher.FILE_RENAMED_OLD_NAME) {
-                synchronized (myDeletedFiles) {
-                  myDeletedFiles.add(path);
-                }
-              }
-            }
-          }
-        }
-      }
-      catch (IOException e) {
-        LOG.info("Watcher terminated and attempt to restart has failed. Exiting watching thread.", e);
-      }
-    }
-  }
-
   private void setUpFileWatcher() {
     final Application application = ApplicationManager.getApplication();
 
     if (application.isDisposeInProgress()) return;
 
-    if (FileWatcher.isAvailable()) {
+    if (myWatcher.isOperational()) {
       application.runReadAction(new Runnable() {
         public void run() {
           WRITE_LOCK.lock();
           try {
             final WatchRequest[] watchRequests = normalizeRootsForRefresh();
-            String[] dirPaths = new String[watchRequests.length];
-            boolean[] toWatchRecursively = new boolean[watchRequests.length];
-            int cnt = 0;
+            List<String> myRecursiveRoots = new ArrayList<String>();
+            List<String> myFlatRoots = new ArrayList<String>();
+
             for (WatchRequest root : watchRequests) {
-              dirPaths[cnt] = root.getFileSystemRootPath();
-              toWatchRecursively[cnt] = root.isToWatchRecursively();
-              cnt++;
+              if (root.isToWatchRecursively()) {
+                myRecursiveRoots.add(root.getFileSystemRootPath());
+              }
+              else {
+                myFlatRoots.add(root.getFileSystemRootPath());
+              }
             }
 
-            final Vector<String> watchManual = new Vector<String>();
-            FileWatcher.setup(dirPaths, toWatchRecursively, watchManual);
-
-            myManualWatchRoots = new ArrayList<String>();
-            for (int i = 0; i < watchManual.size(); i++) {
-              myManualWatchRoots.add(watchManual.elementAt(i));
-            }
+            myWatcher.setWatchRoots(myRecursiveRoots, myFlatRoots);
           }
           finally {
             WRITE_LOCK.unlock();
@@ -469,7 +420,6 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
     }
 
     public void run() {
-      //noinspection InfiniteLoopStatement
       while (true) {
         final Application application = ApplicationManager.getApplication();
         if (application == null || application.isDisposed()) break;
@@ -897,7 +847,7 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
       }
     };
 
-    if (asynchronous && FileWatcher.isAvailable()) {
+    if (asynchronous && myWatcher.isOperational()) {
       RefreshQueue.getInstance().refresh(true, true, heavyRefresh, ManagingFS.getInstance().getRoots(this));
     }
     else {
index a464d0102027ddaae3acaa93b058c4161426a718..ab26913cb72d77fb298c0f8985a8896a0ca36b55 100644 (file)
       <implementation-class>com.intellij.openapi.vfs.newvfs.persistent.PersistentFS</implementation-class>
     </component>
 
-    <component>
-      <interface-class>com.intellij.openapi.vfs.watcher.FileSystemTracker</interface-class>
-      <implementation-class>com.intellij.openapi.vfs.impl.watcher.FileSystemTrackerImpl</implementation-class>
-    </component>
-
     <component>
       <interface-class>com.intellij.openapi.vfs.pointers.VirtualFilePointerManager</interface-class>
       <implementation-class>com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl</implementation-class>
diff --git a/tools/fsNotifier/mac/fsnotifier.c b/tools/fsNotifier/mac/fsnotifier.c
new file mode 100644 (file)
index 0000000..bae724d
--- /dev/null
@@ -0,0 +1,176 @@
+#include <CoreServices/CoreServices.h>
+#include <sys/mount.h>
+
+static int ReportMountedFileSystems()
+    // If fsBuf is too small to account for all volumes, getfsstat will 
+    // silently truncate the returned information.  Worse yet, it returns 
+    // the number of volumes it passed back, not the number of volumes present, 
+    // so you can't tell if the list was truncated. 
+    //
+    // So, in order to get an accurate snapshot of the volume list, I call 
+    // getfsstat with a NULL fsBuf to get a count (fsCountOrig), then allocate a 
+    // buffer that holds (fsCountOrig + 1) items, then call getfsstat again with 
+    // that buffer.  If the list was silently truncated, the second count (fsCount)
+    // will be (fsCountOrig + 1), and we loop to try again.
+{
+    int                 err;
+    int                 fsCountOrig;
+    int                 fsCount;
+    struct statfs *     fsBuf;
+    bool                done;
+
+
+    fsBuf = NULL;
+    fsCount = 0;
+    
+    done = false;
+    do {
+        // Get the initial count.
+        err = 0;
+        fsCountOrig = getfsstat(NULL, 0, MNT_WAIT);
+        if (fsCountOrig < 0) {
+            err = errno;
+        }
+        
+        // Allocate a buffer for fsCountOrig + 1 items.
+        if (err == 0) {
+            if (fsBuf != NULL) {
+                free(fsBuf);
+            }
+            fsBuf = malloc((fsCountOrig + 1) * sizeof(*fsBuf));
+            if (fsBuf == NULL) {
+                err = ENOMEM;
+            }
+        }
+        
+        // Get the list.  
+        if (err == 0) {
+            fsCount = getfsstat(fsBuf, (int) ((fsCountOrig + 1) * sizeof(*fsBuf)), MNT_WAIT);
+            if (fsCount < 0) {
+                err = errno;
+            }
+        }
+        
+        // We got the full list if the number of items returned by the kernel 
+        // is strictly less than the buffer that we allocated (fsCountOrig + 1).
+        if (err == 0) {
+            if (fsCount <= fsCountOrig) {
+                done = true;
+            }
+        }
+    } while ( (err == 0) && ! done );
+
+    int i;
+    int mountCounts = 0;
+    for (i = 0; i < fsCount; i++) {
+        if ((fsBuf[i].f_flags & MNT_LOCAL) == 0 || (fsBuf[i].f_flags & MNT_JOURNALED) == 0) {
+            if (mountCounts == 0) {
+              printf("UNWATCHEABLE\n");
+            }
+            printf("%s\n", fsBuf[i].f_mntonname);
+            mountCounts++;
+        }
+    }
+
+    if (mountCounts > 0) {
+      printf("#\n");
+      fflush(stdout);
+    }
+
+    free(fsBuf);
+    fsBuf = NULL;
+    
+    return err;
+}
+
+void callback(ConstFSEventStreamRef streamRef,
+              void *clientCallBackInfo,
+              size_t numEvents,
+              void *eventPaths,
+              const FSEventStreamEventFlags eventFlags[],
+              const FSEventStreamEventId eventIds[]) {
+    char **paths = eventPaths;
+    int i;
+    for (i=0; i<numEvents; i++) {
+      FSEventStreamEventFlags flags = eventFlags[i];
+      if (flags == kFSEventStreamEventFlagMount || flags == kFSEventStreamEventFlagUnmount) {
+        ReportMountedFileSystems();
+      }
+      else if ((flags & kFSEventStreamEventFlagMustScanSubDirs) != 0) {
+        printf("RECDIRTY\n");
+        printf("%s\n", paths[i]);
+      }
+      else if (eventFlags[i] != kFSEventStreamEventFlagNone) {
+        printf("RESET\n");
+      }
+      else {
+        printf("DIRTY\n");
+        printf("%s\n", paths[i]);
+      }
+    }
+
+    fflush(stdout);
+}
+
+// Static buffer for fscanf. All of the are being performed from a single thread, so it's thread safe.
+static char command[2048];
+
+static void parseRoots() {
+    while (TRUE) {
+     fscanf(stdin, "%s", command);
+     if (strcmp(command, "#") == 0 || feof(stdin)) break;
+    }
+}
+
+void *event_processing_thread(void *data) {
+    CFStringRef mypath = CFSTR("/");
+    CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, 1, NULL);
+    void *callbackInfo = NULL;
+
+    FSEventStreamRef stream;
+    CFAbsoluteTime latency = 0.3; /* Latency in seconds */
+    // Create the stream, passing in a callback,
+    stream = FSEventStreamCreate(NULL,
+        &callback,
+        callbackInfo,
+        pathsToWatch,
+        kFSEventStreamEventIdSinceNow,
+        latency,
+        kFSEventStreamCreateFlagNoDefer
+    );
+
+    FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+    FSEventStreamStart(stream);
+
+    CFRunLoopRun();
+    return NULL;
+}
+
+int main (int argc, const char * argv[]) {
+    // Checking if necessary API is available (need MacOS X 10.5 or later).
+    if (FSEventStreamCreate == NULL) {
+      printf("GIVEUP\n");
+      return 1;
+    }
+
+    ReportMountedFileSystems();
+
+    pthread_t thread_id;
+    int rc = pthread_create(&thread_id, NULL, event_processing_thread, NULL);
+
+    if (rc != 0) {
+      // Give up if cannot create a thread.
+      printf("GIVEUP\n");
+      exit(1);
+    }
+
+    while (TRUE) {
+      fscanf(stdin, "%s", command); 
+      if (strcmp(command, "EXIT") == 0 || feof(stdin)) exit(0);
+      if (strcmp(command, "ROOTS") == 0) parseRoots();
+    }
+    return 0;
+}
diff --git a/tools/fsNotifier/mac/make.sh b/tools/fsNotifier/mac/make.sh
new file mode 100644 (file)
index 0000000..7935b81
--- /dev/null
@@ -0,0 +1 @@
+gcc -framework CoreServices -o fsnotifier fsnotifier.c
\ No newline at end of file