FileWatcher refactoring

This commit is contained in:
Maxim Shafirov
2008-09-24 21:31:14 +04:00
parent 0e58da2636
commit e4ad3771b1
8 changed files with 540 additions and 103 deletions
BIN
View File
Binary file not shown.
@@ -0,0 +1,14 @@
/*
* @author max
*/
package com.intellij.openapi.vfs.watcher;
public enum ChangeKind {
CREATE,
DELETE,
STATS,
CHANGE,
DIRTY,
RECDIRTY,
RESET
}
@@ -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;
@@ -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();
}
}
}
}
@@ -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,21 +278,15 @@ 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 String[] dirtyFiles) {
private void markPathsDirty(final List<String> dirtyFiles) {
for (String dirtyFile : dirtyFiles) {
String path = dirtyFile.replace(File.separatorChar, '/');
VirtualFile file = findFileByPathIfCached(path);
@@ -315,11 +296,35 @@ public final class LocalFileSystemImpl extends LocalFileSystem implements Applic
}
}
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) {
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();
@@ -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 {
@@ -30,11 +30,6 @@
<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>
+176
View File
@@ -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;
}
+1
View File
@@ -0,0 +1 @@
gcc -framework CoreServices -o fsnotifier fsnotifier.c