Skip to content
Browse files

Added FSEvents file watcher and cleaned-up version of UKKQueue.

git-svn-id: svn://witness.is-a-geek.org/svn@4 378c4bed-2673-4746-83ae-d22ddc8c5b7c
  • Loading branch information...
1 parent dc5be1c commit de62e9e5d551e92357fe0affc1b6cfeb42733c24 uli committed Jul 4, 2009
Showing with 725 additions and 262 deletions.
  1. +49 −0 UKFSEventsWatcher.h
  2. +292 −0 UKFSEventsWatcher.m
  3. +15 −22 UKKQueue.h
  4. +369 −240 UKKQueue.m
View
49 UKFSEventsWatcher.h
@@ -0,0 +1,49 @@
+/* =============================================================================
+ FILE: UKFSEventsWatcher.h
+
+ COPYRIGHT: (c) 2008 Peter Baumgartner, all rights reserved.
+
+ AUTHORS: Peter Baumgartner
+
+ LICENSES: MIT License
+
+ REVISIONS:
+ 2008-06-09 PB Created.
+ ========================================================================== */
+
+// -----------------------------------------------------------------------------
+// Headers:
+// -----------------------------------------------------------------------------
+
+#import <Cocoa/Cocoa.h>
+#import "UKFileWatcher.h"
+#import <Carbon/Carbon.h>
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
+
+// -----------------------------------------------------------------------------
+// Class declaration:
+// -----------------------------------------------------------------------------
+
+@interface UKFSEventsWatcher : NSObject <UKFileWatcher>
+{
+ id delegate; // Delegate must respond to UKFileWatcherDelegate protocol.
+ CFTimeInterval latency; // Time that must pass before events are being sent.
+ FSEventStreamCreateFlags flags; // See FSEvents.h
+ NSMutableDictionary* eventStreams; // List of FSEventStreamRef pointers in NSValues, with the pathnames as their keys.
+}
+
++ (id) sharedFileWatcher;
+
+- (void) setLatency:(CFTimeInterval)latency;
+- (CFTimeInterval) latency;
+
+- (void) setFSEventStreamCreateFlags:(FSEventStreamCreateFlags)flags;
+- (FSEventStreamCreateFlags) fsEventStreamCreateFlags;
+
+// UKFileWatcher defines the methods: addPath: removePath: and delegate accessors.
+- (void) removeAllPaths;
+
+@end
+
+#endif
View
292 UKFSEventsWatcher.m
@@ -0,0 +1,292 @@
+/* =============================================================================
+ FILE: UKFSEventsWatcher.m
+
+ COPYRIGHT: (c) 2008 Peter Baumgartner, all rights reserved.
+
+ AUTHORS: Peter Baumgartner
+
+ LICENSES: MIT License
+
+ REVISIONS:
+ 2008-06-09 PB Created.
+ ========================================================================== */
+
+// -----------------------------------------------------------------------------
+// Headers:
+// -----------------------------------------------------------------------------
+
+#import "UKFSEventsWatcher.h"
+#import <CoreServices/CoreServices.h>
+
+
+// -----------------------------------------------------------------------------
+// FSEventCallback
+// Private callback that is called by the FSEvents framework
+// -----------------------------------------------------------------------------
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
+
+static void FSEventCallback(ConstFSEventStreamRef inStreamRef,
+ void* inClientCallBackInfo,
+ size_t inNumEvents,
+ void* inEventPaths,
+ const FSEventStreamEventFlags inEventFlags[],
+ const FSEventStreamEventId inEventIds[])
+{
+ UKFSEventsWatcher* watcher = (UKFSEventsWatcher*)inClientCallBackInfo;
+
+ if (watcher != nil && [watcher delegate] != nil)
+ {
+ id delegate = [watcher delegate];
+
+ if ([delegate respondsToSelector:@selector(watcher:receivedNotification:forPath:)])
+ {
+ NSEnumerator* paths = [(NSArray*)inEventPaths objectEnumerator];
+ NSString* path;
+
+ while (path = [paths nextObject])
+ {
+ [delegate watcher:watcher receivedNotification:UKFileWatcherWriteNotification forPath:path];
+
+ [[[NSWorkspace sharedWorkspace] notificationCenter]
+ postNotificationName: UKFileWatcherWriteNotification
+ object:watcher
+ userInfo:[NSDictionary dictionaryWithObjectsAndKeys:path,@"path",nil]];
+ }
+ }
+ }
+}
+
+
+@implementation UKFSEventsWatcher
+
+// -----------------------------------------------------------------------------
+// sharedFileWatcher:
+// Singleton accessor.
+// -----------------------------------------------------------------------------
+
++(id) sharedFileWatcher
+{
+ static UKFSEventsWatcher* sSharedFileWatcher = nil;
+ static NSString* sSharedFileWatcherMutex = @"UKFSEventsWatcher";
+
+ @synchronized(sSharedFileWatcherMutex)
+ {
+ if (sSharedFileWatcher == nil)
+ {
+ sSharedFileWatcher = [[UKFSEventsWatcher alloc] init]; // This is a singleton, and thus an intentional "leak".
+ }
+ }
+
+ return sSharedFileWatcher;
+}
+
+
+// -----------------------------------------------------------------------------
+// * CONSTRUCTOR:
+// -----------------------------------------------------------------------------
+
+-(id) init
+{
+ if (self = [super init])
+ {
+ latency = 1.0;
+ flags = kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot;
+ eventStreams = [[NSMutableDictionary alloc] init];
+ }
+
+ return self;
+}
+
+
+// -----------------------------------------------------------------------------
+// * DESTRUCTOR:
+// -----------------------------------------------------------------------------
+
+-(void) dealloc
+{
+ [self removeAllPaths];
+ [eventStreams release];
+ [super dealloc];
+}
+
+-(void) finalize
+{
+ [self removeAllPaths];
+ [super finalize];
+}
+
+
+// -----------------------------------------------------------------------------
+// setLatency:
+// Time that must pass before events are being sent.
+// -----------------------------------------------------------------------------
+
+- (void) setLatency:(CFTimeInterval)inLatency
+{
+ latency = inLatency;
+}
+
+
+// -----------------------------------------------------------------------------
+// latency
+// Time that must pass before events are being sent.
+// -----------------------------------------------------------------------------
+
+- (CFTimeInterval) latency
+{
+ return latency;
+}
+
+
+// -----------------------------------------------------------------------------
+// setFSEventStreamCreateFlags:
+// See FSEvents.h for meaning of these flags.
+// -----------------------------------------------------------------------------
+
+- (void) setFSEventStreamCreateFlags:(FSEventStreamCreateFlags)inFlags
+{
+ flags = inFlags;
+}
+
+
+// -----------------------------------------------------------------------------
+// fsEventStreamCreateFlags
+// See FSEvents.h for meaning of these flags.
+// -----------------------------------------------------------------------------
+
+- (FSEventStreamCreateFlags) fsEventStreamCreateFlags
+{
+ return flags;
+}
+
+
+// -----------------------------------------------------------------------------
+// setDelegate:
+// Mutator for file watcher delegate.
+// -----------------------------------------------------------------------------
+
+-(void) setDelegate: (id)newDelegate
+{
+ delegate = newDelegate;
+}
+
+
+// -----------------------------------------------------------------------------
+// delegate:
+// Accessor for file watcher delegate.
+// -----------------------------------------------------------------------------
+
+-(id) delegate
+{
+ return delegate;
+}
+
+
+// -----------------------------------------------------------------------------
+// parentFolderForFilePath:
+// We need to supply a folder to FSEvents, so if we were passed a path
+// to a file, then convert it to the parent folder path...
+// -----------------------------------------------------------------------------
+
+- (NSString*) pathToParentFolderOfFile:(NSString*)inPath
+{
+ BOOL directory;
+ BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:inPath isDirectory:&directory];
+ BOOL package = [[NSWorkspace sharedWorkspace] isFilePackageAtPath:inPath];
+
+ if (exists && directory==NO && package==NO)
+ {
+ inPath = [inPath stringByDeletingLastPathComponent];
+ }
+
+ return inPath;
+}
+
+
+// -----------------------------------------------------------------------------
+// addPath:
+// Start watching the folder at the specified path.
+// -----------------------------------------------------------------------------
+
+-(void) addPath: (NSString*)path
+{
+ path = [self pathToParentFolderOfFile:path];
+ NSArray* paths = [NSArray arrayWithObject:path];
+
+ FSEventStreamContext context;
+ context.version = 0;
+ context.info = (void*) self;
+ context.retain = NULL;
+ context.release = NULL;
+ context.copyDescription = NULL;
+
+ FSEventStreamRef stream = FSEventStreamCreate(NULL,&FSEventCallback,&context,(CFArrayRef)paths,kFSEventStreamEventIdSinceNow,latency,flags);
+
+ if (stream)
+ {
+ FSEventStreamScheduleWithRunLoop(stream,CFRunLoopGetMain(),kCFRunLoopCommonModes);
+ FSEventStreamStart(stream);
+
+ @synchronized (self)
+ {
+ [eventStreams setObject:[NSValue valueWithPointer:stream] forKey:path];
+ }
+ }
+ else
+ {
+ NSLog( @"UKFSEventsWatcher addPath:%@ failed",path);
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+// removePath:
+// Stop watching the folder at the specified path.
+// -----------------------------------------------------------------------------
+
+-(void) removePath: (NSString*)path
+{
+ NSValue* value = nil;
+
+ @synchronized (self)
+ {
+ value = [[[eventStreams objectForKey:path] retain] autorelease];
+ [eventStreams removeObjectForKey:path];
+ }
+
+ if (value)
+ {
+ FSEventStreamRef stream = [value pointerValue];
+
+ if (stream)
+ {
+ FSEventStreamStop(stream);
+ FSEventStreamInvalidate(stream);
+ FSEventStreamRelease(stream);
+ }
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+// removeAllPaths:
+// Stop watching all known folders.
+// -----------------------------------------------------------------------------
+
+-(void) removeAllPaths
+{
+ NSEnumerator* paths = [[eventStreams allKeys] objectEnumerator];
+ NSString* path;
+
+ while (path = [paths nextObject])
+ {
+ [self removePath:path];
+ }
+}
+
+
+@end
+
+#endif
+
View
37 UKKQueue.h
@@ -40,45 +40,38 @@
#define UKKQueueNotifyAboutLinkCountChanged NOTE_LINK // Item's link count changed.
#define UKKQueueNotifyAboutAccessRevocation NOTE_REVOKE // Access to item was revoked.
+#define UKKQueueNotifyDefault (UKKQueueNotifyAboutRename | UKKQueueNotifyAboutWrite \
+ | UKKQueueNotifyAboutDelete | UKKQueueNotifyAboutAttributeChange \
+ | UKKQueueNotifyAboutSizeIncrease | UKKQueueNotifyAboutLinkCountChanged \
+ | UKKQueueNotifyAboutAccessRevocation)
// -----------------------------------------------------------------------------
// UKKQueue:
// -----------------------------------------------------------------------------
-/*
- WARNING: A kqueue retains itself while it is watching paths. If you want
- to make sure a kqueue gets released, call removeAllPaths on it
- before you release it.
-*/
-
@interface UKKQueue : NSObject <UKFileWatcher>
{
- int queueFD; // The actual queue ID (Unix file descriptor).
- NSMutableArray* watchedPaths; // List of NSStrings containing the paths we're watching. These match up with watchedFDs, as a dictionary that may have duplicate keys.
- NSMutableArray* watchedFDs; // List of NSNumbers containing the file descriptors we're watching. These match up with watchedPaths, as a dictionary that may have duplicate keys.
- id delegate; // Gets messages about changes instead of notification center, if specified.
- id delegateProxy; // Proxy object to which we send messages so they reach delegate on the main thread.
- BOOL alwaysNotify; // Send notifications even if we have a delegate? Defaults to NO for alloc/inited instances, YES for the singleton.
- BOOL keepThreadRunning; // Termination criterion of our thread.
- BOOL threadHasTerminated; // Feedback whether our thread has successfully quit.
+ NSMutableDictionary* watchedFiles; // List of NSStrings containing the paths we're watching. These match up with watchedFDs, as a dictionary that may have duplicate keys.
+ id delegate; // Gets messages about changes instead of notification center, if specified.
+ BOOL alwaysNotify; // Send notifications with us as the object even when we have a delegate.
}
+(id) sharedFileWatcher; // Returns a singleton, a shared kqueue object. Handy if you're subscribing to the notifications. Use this, or just create separate objects using alloc/init. Whatever floats your boat.
-(int) queueFD; // I know you unix geeks want this...
+-(BOOL) alwaysNotify;
+-(void) setAlwaysNotify: (BOOL)state;
+
// High-level file watching:
--(void) addPath: (NSString*)path; // UKFileWatcher protocol, preferred.
+-(void) addPath: (NSString*)path; // UKFileWatcher protocol, preferred.
-(void) addPath: (NSString*)path notifyingAbout: (u_int)fflags;
--(void) removePath: (NSString*)path; // UKFileWatcher protocol.
--(void) removeAllPaths; // UKFileWatcher protocol.
+-(void) removePath: (NSString*)path; // UKFileWatcher protocol.
+-(void) removeAllPaths; // UKFileWatcher protocol.
// For alloc/inited instances, you can specify a delegate instead of subscribing for notifications:
--(void) setDelegate: (id)newDelegate; // UKFileWatcher protocol.
--(id) delegate; // UKFileWatcher protocol.
-
--(void) setAlwaysNotify: (BOOL)n; // Send notifications even if we have a delegate.
--(BOOL) alwaysNotify;
+-(void) setDelegate: (id)newDelegate; // UKFileWatcher protocol.
+-(id) delegate; // UKFileWatcher protocol.
@end
View
609 UKKQueue.m
@@ -27,67 +27,157 @@
#import <fcntl.h>
#include <sys/stat.h>
-
// -----------------------------------------------------------------------------
// Macros:
// -----------------------------------------------------------------------------
#define DEBUG_LOG_THREAD_LIFETIME 1
#define DEBUG_DETAILED_MESSAGES 1
+#ifndef MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
+#define NSUInteger unsigned
+#endif
// -----------------------------------------------------------------------------
-// Private stuff:
+// Helper class:
// -----------------------------------------------------------------------------
-@interface UKKQueue (UKPrivateMethods)
+@interface UKKQueuePathEntry : NSObject
+{
+ NSString* path;
+ int watchedFD;
+ u_int subscriptionFlags;
+ int pathRefCount;
+}
--(void) watcherThread: (id)sender;
--(void) postNotification: (NSString*)nm forFile: (NSString*)fp; // Message-posting bottleneck.
+-(id) initWithPath: (NSString*)inPath flags: (u_int)fflags;
+
+-(void) retainPath;
+-(BOOL) releasePath;
+
+-(NSString*) path;
+-(int) watchedFD;
+
+-(u_int) subscriptionFlags;
+-(void) setSubscriptionFlags: (u_int)fflags;
@end
+@implementation UKKQueuePathEntry
-// -----------------------------------------------------------------------------
-// Globals:
-// -----------------------------------------------------------------------------
+-(id) initWithPath: (NSString*)inPath flags: (u_int)fflags;
+{
+ if(( self = [super init] ))
+ {
+ path = [inPath copy];
+ watchedFD = open( [path fileSystemRepresentation], O_EVTONLY, 0 );
+ if( watchedFD < 0 )
+ {
+ [self autorelease];
+ return nil;
+ }
+ subscriptionFlags = fflags;
+ pathRefCount = 1;
+ }
+
+ return self;
+}
-static UKKQueue * gUKKQueueSharedQueueSingleton = nil;
-static id gUKKQueueSharedNotificationCenterProxy = nil; // Object to which we send notifications so they get put in the main thread.
+-(void) dealloc
+{
+ [path release];
+ path = nil;
+ if( watchedFD >= 0 )
+ close(watchedFD);
+ watchedFD = -1;
+ pathRefCount = 0;
+
+ [super dealloc];
+}
+
+-(void) retainPath
+{
+ @synchronized( self )
+ {
+ pathRefCount++;
+ }
+}
+
+-(BOOL) releasePath
+{
+ @synchronized( self )
+ {
+ pathRefCount--;
+
+ return (pathRefCount == 0);
+ }
+
+ return NO;
+}
+
+-(NSString*) path
+{
+ return path;
+}
+
+-(int) watchedFD
+{
+ return watchedFD;
+}
+
+-(u_int) subscriptionFlags
+{
+ return subscriptionFlags;
+}
+
+-(void) setSubscriptionFlags: (u_int)fflags
+{
+ subscriptionFlags = fflags;
+}
+
+
+@end
-@implementation UKKQueue
// -----------------------------------------------------------------------------
-// sharedQueue:
-// Returns a singleton queue object. In many apps (especially those that
-// subscribe to the notifications) there will only be one kqueue instance,
-// and in that case you can use this.
-//
-// For all other cases, feel free to create additional instances to use
-// independently.
-//
-// REVISIONS:
-// 2008-11-07 UK Made this always notify, in case some nit sets a
-// delegate on the singleton.
-// 2006-03-13 UK Renamed from sharedQueue.
-// 2005-07-02 UK Created.
+// Private stuff:
// -----------------------------------------------------------------------------
-+(id) sharedFileWatcher
+@interface UKKQueueCentral : NSObject
{
- @synchronized( self )
- {
- if( !gUKKQueueSharedQueueSingleton )
- {
- gUKKQueueSharedQueueSingleton = [[UKKQueue alloc] init]; // This is a singleton, and thus an intentional "leak".
- [gUKKQueueSharedQueueSingleton setAlwaysNotify: YES];
- }
- }
-
- return gUKKQueueSharedQueueSingleton;
+ int queueFD; // The actual queue ID (Unix file descriptor).
+ NSMutableDictionary* watchedFiles; // List of UKKQueuePathEntries.
+ BOOL keepThreadRunning;
}
+-(int) queueFD; // I know you unix geeks want this...
+
+// UKFileWatcher protocol methods:
+-(void) addPath: (NSString*)path;
+-(void) addPath: (NSString*)path notifyingAbout: (u_int)fflags;
+-(void) removePath: (NSString*)path;
+-(void) removeAllPaths;
+
+// Main bottleneck for subscribing:
+-(UKKQueuePathEntry*) addPathToQueue: (NSString*)path notifyingAbout: (u_int)fflags;
+
+// Actual work is done here:
+-(void) watcherThread: (id)sender;
+-(void) postNotification: (NSString*)nm forFile: (NSString*)fp; // Message-posting bottleneck.
+
+@end
+
+
+// -----------------------------------------------------------------------------
+// Globals:
+// -----------------------------------------------------------------------------
+
+static UKKQueueCentral * gUKKQueueSharedQueueSingleton = nil;
+static id gUKKQueueSharedNotificationCenterProxy = nil; // Object to which we send notifications so they get put in the main thread.
+
+
+@implementation UKKQueueCentral
// -----------------------------------------------------------------------------
// * CONSTRUCTOR:
@@ -120,10 +210,7 @@ -(id) init
return nil;
}
- watchedPaths = [[NSMutableArray alloc] init];
- watchedFDs = [[NSMutableArray alloc] init];
-
- threadHasTerminated = YES;
+ watchedFiles = [[NSMutableDictionary alloc] init];
}
return self;
@@ -141,38 +228,15 @@ -(id) init
-(void) dealloc
{
- delegate = nil;
- [delegateProxy release];
+ keepThreadRunning = NO;
// Close all our file descriptors so the files can be deleted:
[self removeAllPaths];
- [watchedPaths release];
- watchedPaths = nil;
- [watchedFDs release];
- watchedFDs = nil;
+ [watchedFiles release];
+ watchedFiles = nil;
[super dealloc];
-
- //NSLog(@"kqueue released.");
-}
-
-
--(void) finalize
-{
- // Close all our file descriptors so the files can be deleted:
- [self removeAllPaths];
-
- [super finalize];
-}
-
-
--(void) invalidate
-{
- @synchronized( self )
- {
- keepThreadRunning = NO;
- }
}
@@ -186,26 +250,12 @@ -(void) invalidate
// 2004-12-28 UK Added as suggested by bbum.
// -----------------------------------------------------------------------------
--(void) unsubscribeAll
-{
- [self removeAllPaths];
-}
-
-(void) removeAllPaths
{
@synchronized( self )
{
- NSEnumerator * fdEnumerator = [watchedFDs objectEnumerator];
- NSNumber * anFD;
-
- while( (anFD = [fdEnumerator nextObject]) != nil )
- close( [anFD intValue] );
-
- [watchedFDs removeAllObjects];
- [watchedPaths removeAllObjects];
-
- [self invalidate];
- }
+ [watchedFiles removeAllObjects];
+ }
}
@@ -223,83 +273,59 @@ -(int) queueFD
return queueFD;
}
-
-// -----------------------------------------------------------------------------
-// addPathToQueue:
-// Tell this queue to listen for all interesting notifications sent for
-// the object at the specified path. If you want more control, use the
-// addPathToQueue:notifyingAbout: variant instead.
-//
-// REVISIONS:
-// 2004-03-13 UK Documented.
-// -----------------------------------------------------------------------------
-
-(void) addPath: (NSString*)path
{
- [self addPath: path notifyingAbout: UKKQueueNotifyAboutRename
- | UKKQueueNotifyAboutWrite
- | UKKQueueNotifyAboutDelete
- | UKKQueueNotifyAboutAttributeChange
- | UKKQueueNotifyAboutSizeIncrease
- | UKKQueueNotifyAboutLinkCountChanged
- | UKKQueueNotifyAboutAccessRevocation ];
+ [self addPath: path notifyingAbout: UKKQueueNotifyDefault];
}
-// -----------------------------------------------------------------------------
-// addPath:notfyingAbout:
-// Tell this queue to listen for the specified notifications sent for
-// the object at the specified path.
-//
-// NOTE: This keeps track of each time you call it. If you subscribe twice,
-// you are expected to unsubscribe twice. This is necessary for the
-// singleton to be reasonably safe to use, because otherwise two
-// objects could subscribe for the same path, and when one of them
-// unsubscribes, it would unsubscribe both.
-//
-// REVISIONS:
-// 2008-11-07 UK Renamed from addPathToQueue:notifyingAbout:
-// 2005-06-29 UK Files are now opened using O_EVTONLY instead of O_RDONLY
-// which allows ejecting or deleting watched files/folders.
-// Thanks to Phil Hargett for finding this flag in the docs.
-// 2004-03-13 UK Documented.
-// -----------------------------------------------------------------------------
-
--(void) addPathToQueue: (NSString*)path notifyingAbout: (u_int)fflags
+-(void) addPath: (NSString*)path notifyingAbout: (u_int)fflags
{
- [self addPath: path notifyingAbout: fflags];
+ [self addPathToQueue: path notifyingAbout: fflags];
}
--(void) addPath: (NSString*)path notifyingAbout: (u_int)fflags
+-(UKKQueuePathEntry*) addPathToQueue: (NSString*)path notifyingAbout: (u_int)fflags
{
- struct timespec nullts = { 0, 0 };
- struct kevent ev;
- int fd = open( [path fileSystemRepresentation], O_EVTONLY, 0 );
-
- if( fd >= 0 )
- {
- EV_SET( &ev, fd, EVFILT_VNODE,
- EV_ADD | EV_ENABLE | EV_CLEAR,
- fflags, 0, (void*)path );
+ @synchronized( self )
+ {
+ UKKQueuePathEntry* pe = [watchedFiles objectForKey: path]; // Already watching this path?
+ if( pe )
+ {
+ [pe retainPath]; // Just add another subscription to this entry.
+
+ if( ([pe subscriptionFlags] & fflags) == fflags ) // All flags already set?
+ return [[pe retain] autorelease];
+
+ fflags |= [pe subscriptionFlags];
+ }
+
+ struct timespec nullts = { 0, 0 };
+ struct kevent ev;
- @synchronized( self )
- {
- [watchedPaths addObject: path];
- [watchedFDs addObject: [NSNumber numberWithInt: fd]];
+ if( !pe )
+ pe = [[[UKKQueuePathEntry alloc] initWithPath: path flags: fflags] autorelease];
+
+ if( pe )
+ {
+ EV_SET( &ev, [pe watchedFD], EVFILT_VNODE,
+ EV_ADD | EV_ENABLE | EV_CLEAR,
+ fflags, 0, pe );
+
+ [pe setSubscriptionFlags: fflags];
+ [watchedFiles setObject: pe forKey: path];
kevent( queueFD, &ev, 1, NULL, 0, &nullts );
// Start new thread that fetches and processes our events:
if( !keepThreadRunning )
{
- while( !threadHasTerminated )
- usleep(10);
-
keepThreadRunning = YES;
- threadHasTerminated = NO;
[NSThread detachNewThreadSelector:@selector(watcherThread:) toTarget:self withObject:nil];
}
}
- }
+ return [[pe retain] autorelease];
+ }
+
+ return nil;
}
@@ -314,93 +340,14 @@ -(void) addPath: (NSString*)path notifyingAbout: (u_int)fflags
-(void) removePath: (NSString*)path
{
- int index = 0;
- int fd = -1;
-
- @synchronized( self )
- {
- index = [watchedPaths indexOfObject: path];
-
- if( index == NSNotFound )
- return;
-
- fd = [[watchedFDs objectAtIndex: index] intValue];
-
- [watchedFDs removeObjectAtIndex: index];
- [watchedPaths removeObjectAtIndex: index];
-
- if( [watchedPaths count] == 0 )
- [self invalidate];
- }
-
- if( close( fd ) == -1 )
- NSLog(@"removePathFromQueue: Couldn't close file descriptor (%d)", errno);
-}
-
-
-/*-(NSString*) filePathForFileDescriptor:(const int)fd oldPath: (NSString*)oldPath
-{
- struct stat fileStatus;
- struct stat currentFileStatus;
-
- // Get file status
- if( fstat(fd, &fileStatus) == -1 )
- return nil;
-
- NSString* basePath = [oldPath stringByDeletingLastPathComponent];
- NSEnumerator *dirEnumerator;
- dirEnumerator = [[NSFileManager defaultManager] enumeratorAtPath: basePath];
-
- NSString *path;
- while( (path = [dirEnumerator nextObject]) )
- {
- NSString *fullPath = [basePath stringByAppendingPathComponent:path];
- if( stat([fullPath fileSystemRepresentation], &currentFileStatus) == 0 )
- {
- if ((currentFileStatus.st_dev == fileStatus.st_dev) &&
- (currentFileStatus.st_ino == fileStatus.st_ino))
- {
- // Found file
- return fullPath;
- }
- }
- }
-
- // Didn't find file
- return nil;
-}*/
-
-
--(id) delegate
-{
- return delegate;
-}
-
--(void) setDelegate: (id)newDelegate
-{
- id oldProxy = delegateProxy;
- delegate = newDelegate;
- delegateProxy = [delegate copyMainThreadProxy];
- [delegateProxy setWaitForCompletion: NO]; // Better performance and avoid deadlocks.
- [oldProxy release];
-}
-
-// -----------------------------------------------------------------------------
-// Flag to send a notification even if we have a delegate:
-// -----------------------------------------------------------------------------
-
--(BOOL) alwaysNotify
-{
- return alwaysNotify;
-}
-
-
--(void) setAlwaysNotify: (BOOL)n
-{
- alwaysNotify = n;
+ @synchronized( self )
+ {
+ UKKQueuePathEntry* pe = [watchedFiles objectForKey: path]; // Already watching this path?
+ if( pe && [pe releasePath] ) // Give up one subscription. Is this the last subscription?
+ [watchedFiles removeObjectForKey: path]; // Unsubscribe from this file.
+ }
}
-
// -----------------------------------------------------------------------------
// description:
// This method can be used to help in debugging. It provides the value
@@ -425,20 +372,14 @@ -(NSString*) descriptionWithLocale: (id)locale indent: (NSUInteger)level
[mutStr appendString: @"{"];
for( x = 0; x < level; x++ )
[mutStr appendString: @" "];
- [mutStr appendFormat: @"watchedPaths = %@", [watchedPaths descriptionWithLocale: locale indent: level +1]];
- for( x = 0; x < level; x++ )
- [mutStr appendString: @" "];
- [mutStr appendFormat: @"alwaysNotify = %@", (alwaysNotify? @"YES" : @"NO")];
+ [mutStr appendFormat: @"watchedFiles = %@", [watchedFiles descriptionWithLocale: locale indent: level +1]];
for( x = 0; x < level; x++ )
[mutStr appendString: @" "];
[mutStr appendString: @"}"];
return mutStr;
}
-@end
-
-@implementation UKKQueue (UKPrivateMethods)
// -----------------------------------------------------------------------------
// watcherThread:
@@ -472,7 +413,6 @@ -(void) watcherThread: (id)sender
NSLog(@"watcherThread started.");
#endif
- threadHasTerminated = NO;
while( keepThreadRunning )
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
@@ -488,7 +428,8 @@ -(void) watcherThread: (id)sender
if( ev.fflags )
{
NSLog( @"KEVENT flags are set" );
- NSString* fpath = [[(NSString *)ev.udata retain] autorelease]; // In case one of the notified folks removes the path.
+ UKKQueuePathEntry* pe = [[(UKKQueuePathEntry*)ev.udata retain] autorelease]; // In case one of the notified folks removes the path.
+ NSString* fpath = [pe path];
[[NSWorkspace sharedWorkspace] noteFileSystemChanged: fpath];
if( (ev.fflags & NOTE_RENAME) == NOTE_RENAME )
@@ -518,8 +459,6 @@ -(void) watcherThread: (id)sender
// Close our kqueue's file descriptor:
if( close( theFD ) == -1 )
NSLog(@"watcherThread: Couldn't close main kqueue (%d)", errno);
-
- threadHasTerminated = YES;
#if DEBUG_LOG_THREAD_LIFETIME
NSLog(@"watcherThread finished.");
@@ -550,15 +489,205 @@ -(void) postNotification: (NSString*)nm forFile: (NSString*)fp
NSLog( @"%@: %@", nm, fp );
#endif
- if( delegate && delegateProxy )
+ [gUKKQueueSharedNotificationCenterProxy postNotificationName: nm object: self
+ userInfo: [NSDictionary dictionaryWithObjectsAndKeys: fp, @"path", nil]]; // The proxy sends the notification on the main thread.
+}
+
+@end
+
+
+@implementation UKKQueue
+
+// -----------------------------------------------------------------------------
+// sharedFileWatcher:
+// Returns a singleton queue object. In many apps (especially those that
+// subscribe to the notifications) there will only be one kqueue instance,
+// and in that case you can use this.
+//
+// For all other cases, feel free to create additional instances to use
+// independently.
+//
+// REVISIONS:
+// 2006-03-13 UK Renamed from sharedQueue.
+// 2005-07-02 UK Created.
+// -----------------------------------------------------------------------------
+
++(id) sharedFileWatcher
+{
+ @synchronized( [UKKQueueCentral class] )
{
- [delegateProxy watcher: self receivedNotification: nm forPath: fp];
+ if( !gUKKQueueSharedQueueSingleton )
+ gUKKQueueSharedQueueSingleton = [[UKKQueueCentral alloc] init]; // This is a singleton, and thus an intentional "leak".
}
+
+ return gUKKQueueSharedQueueSingleton;
+}
+
+
+-(id) init
+{
+ if(( self = [super init] ))
+ {
+ watchedFiles = [[NSMutableDictionary alloc] init];
+ NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter];
+ UKKQueueCentral* kqc = [[self class] sharedFileWatcher];
+ [nc addObserver: self selector: @selector(fileChangeNotification:)
+ name: UKFileWatcherRenameNotification object: kqc];
+ [nc addObserver: self selector: @selector(fileChangeNotification:)
+ name: UKFileWatcherWriteNotification object: kqc];
+ [nc addObserver: self selector: @selector(fileChangeNotification:)
+ name: UKFileWatcherDeleteNotification object: kqc];
+ [nc addObserver: self selector: @selector(fileChangeNotification:)
+ name: UKFileWatcherAttributeChangeNotification object: kqc];
+ [nc addObserver: self selector: @selector(fileChangeNotification:)
+ name: UKFileWatcherSizeIncreaseNotification object: kqc];
+ [nc addObserver: self selector: @selector(fileChangeNotification:)
+ name: UKFileWatcherLinkCountChangeNotification object: kqc];
+ [nc addObserver: self selector: @selector(fileChangeNotification:)
+ name: UKFileWatcherAccessRevocationNotification object: kqc];
+ }
- if( !delegateProxy || alwaysNotify )
+ return self;
+}
+
+
+-(void) finalize
+{
+ [self removeAllPaths];
+
+ [super finalize];
+}
+
+
+-(void) dealloc
+{
+ delegate = nil;
+
+ // Close all our file descriptors so the files can be deleted:
+ [self removeAllPaths];
+
+ [watchedFiles release];
+ watchedFiles = nil;
+
+ NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter];
+ UKKQueueCentral* kqc = [[self class] sharedFileWatcher];
+ [nc removeObserver: self
+ name: UKFileWatcherRenameNotification object: kqc];
+ [nc removeObserver: self
+ name: UKFileWatcherWriteNotification object: kqc];
+ [nc removeObserver: self
+ name: UKFileWatcherDeleteNotification object: kqc];
+ [nc removeObserver: self
+ name: UKFileWatcherAttributeChangeNotification object: kqc];
+ [nc removeObserver: self
+ name: UKFileWatcherSizeIncreaseNotification object: kqc];
+ [nc removeObserver: self
+ name: UKFileWatcherLinkCountChangeNotification object: kqc];
+ [nc removeObserver: self
+ name: UKFileWatcherAccessRevocationNotification object: kqc];
+
+ [super dealloc];
+}
+
+
+-(int) queueFD
+{
+ return [[UKKQueue sharedFileWatcher] queueFD]; // We're all one big, happy family now.
+}
+
+// -----------------------------------------------------------------------------
+// addPath:
+// Tell this queue to listen for all interesting notifications sent for
+// the object at the specified path. If you want more control, use the
+// addPath:notifyingAbout: variant instead.
+//
+// REVISIONS:
+// 2004-03-13 UK Documented.
+// -----------------------------------------------------------------------------
+
+-(void) addPath: (NSString*)path
+{
+ [self addPath: path notifyingAbout: UKKQueueNotifyDefault];
+}
+
+
+// -----------------------------------------------------------------------------
+// addPath:notfyingAbout:
+// Tell this queue to listen for the specified notifications sent for
+// the object at the specified path.
+// -----------------------------------------------------------------------------
+
+-(void) addPath: (NSString*)path notifyingAbout: (u_int)fflags
+{
+ UKKQueuePathEntry* entry = [watchedFiles objectForKey: path];
+ if( entry )
+ return; // Already have this one.
+
+ entry = [[UKKQueue sharedFileWatcher] addPathToQueue: path notifyingAbout: fflags];
+ [watchedFiles setObject: entry forKey: path];
+}
+
+
+-(void) removePath: (NSString*)fpath
+{
+ UKKQueuePathEntry* entry = [watchedFiles objectForKey: fpath];
+ if( entry ) // Don't have this one, do nothing.
+ {
+ [watchedFiles removeObjectForKey: fpath];
+ [[UKKQueue sharedFileWatcher] removePath: fpath];
+ }
+}
+
+
+-(id) delegate
+{
+ return delegate;
+}
+
+
+-(void) setDelegate: (id)newDelegate
+{
+ delegate = newDelegate;
+}
+
+
+-(BOOL) alwaysNotify
+{
+ return alwaysNotify;
+}
+
+
+-(void) setAlwaysNotify: (BOOL)state
+{
+ alwaysNotify = state;
+}
+
+
+-(void) removeAllPaths
+{
+ NSEnumerator* enny = [watchedFiles objectEnumerator];
+ UKKQueuePathEntry* entry = nil;
+ UKKQueueCentral* sfw = [UKKQueue sharedFileWatcher];
+
+ // Unsubscribe all:
+ while(( entry = [enny nextObject] ))
+ [sfw removePath: [entry path]];
+
+ [watchedFiles removeAllObjects]; // Empty the list now we don't have any subscriptions anymore.
+}
+
+
+-(void) fileChangeNotification: (NSNotification*)notif
+{
+ NSString* fp = [[notif userInfo] objectForKey: @"path"];
+ NSString* nm = [notif name];
+ if( [watchedFiles objectForKey: fp] == nil ) // Don't notify about files we don't care about.
+ return;
+ [delegate watcher: self receivedNotification: nm forPath: fp];
+ if( !delegate || alwaysNotify )
{
- [gUKKQueueSharedNotificationCenterProxy postNotificationName: nm object: self
- userInfo: [NSDictionary dictionaryWithObjectsAndKeys: fp, @"path", nil]]; // The proxy sends the notification on the main thread.
+ [[[NSWorkspace sharedWorkspace] notificationCenter] postNotificationName: nm object: self
+ userInfo: [notif userInfo]]; // Send the notification on to *our* clients only.
}
}

0 comments on commit de62e9e

Please sign in to comment.
Something went wrong with that request. Please try again.