--- /dev/null
+/*
+ * @author max
+ */
+package com.intellij.openapi.vfs.watcher;
+
+public enum ChangeKind {
+ CREATE,
+ DELETE,
+ STATS,
+ CHANGE,
+ DIRTY,
+ RECDIRTY,
+ RESET
+}
\ No newline at end of file
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;
--- /dev/null
+/*
+ * @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
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;
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;
}
public LocalFileSystemImpl() {
- if (FileWatcher.isAvailable()) {
- FileWatcher.initialize();
- new WatchForChangesThread().start();
+ myWatcher = FileWatcher.getInstance();
+ if (myWatcher.isOperational()) {
new StoreRefreshStatusThread().start();
}
}
}
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;
}
}
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();
}
}
}
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();
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();
}
public void run() {
- //noinspection InfiniteLoopStatement
while (true) {
final Application application = ApplicationManager.getApplication();
if (application == null || application.isDisposed()) break;
}
};
- if (asynchronous && FileWatcher.isAvailable()) {
+ if (asynchronous && myWatcher.isOperational()) {
RefreshQueue.getInstance().refresh(true, true, heavyRefresh, ManagingFS.getInstance().getRoots(this));
}
else {
<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>
--- /dev/null
+#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;
+}
--- /dev/null
+gcc -framework CoreServices -o fsnotifier fsnotifier.c
\ No newline at end of file