Skip to content

Commit

Permalink
Switch to NSCountedSet for tracking the "retain counts" of paths we a…
Browse files Browse the repository at this point in the history
…re watching.
  • Loading branch information
danielpunkass committed Apr 28, 2012
1 parent 2b21920 commit d9df722
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 79 deletions.
18 changes: 15 additions & 3 deletions UKFSEventsWatcher.h
Expand Up @@ -31,7 +31,7 @@
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.
NSMutableDictionary* pathReferenceCounts;// To support a client adding the same path multiple times, we
NSCountedSet* eventStreamPaths; // To support a client adding the same path multiple times, we
// count the number of times it's been added, and only remove when
// the number goes to zero...
}
Expand All @@ -44,8 +44,20 @@
- (void) setFSEventStreamCreateFlags:(FSEventStreamCreateFlags)flags;
- (FSEventStreamCreateFlags) fsEventStreamCreateFlags;

// UKFileWatcher defines the methods: addPath: removePath: and delegate accessors.
- (void) removeAllPaths;
// UKFileWatcher defines the methods: addPath: removePath: removeAllPaths: and delegate accessors.
//
// Our implementation differs from the basic UKFileWatcher protocol in that calls to
// addPath and removePath are expected to be balanced so that if for example addPath:
// is called twice with the same path, it should be called twice with removePath: to
// effect the actual ending of the FSEvent observation.
//
// removeAllPaths ensures that every watched path is no longer watched, regardless
// of the number of times addPath: has been called on a given path.
//

- (void) addPath: (NSString*)path;
- (void) removePath: (NSString*)path;
- (void) removeAllPaths;

@end

Expand Down
156 changes: 80 additions & 76 deletions UKFSEventsWatcher.m
Expand Up @@ -63,7 +63,7 @@ @implementation UKFSEventsWatcher
// Singleton accessor.
// -----------------------------------------------------------------------------

+(id) sharedFileWatcher
+ (id) sharedFileWatcher
{
static UKFSEventsWatcher* sSharedFileWatcher = nil;
static NSString* sSharedFileWatcherMutex = @"UKFSEventsWatcher";
Expand All @@ -83,14 +83,14 @@ +(id) sharedFileWatcher
// * CONSTRUCTOR:
// -----------------------------------------------------------------------------

-(id) init
- (id) init
{
if (self = [super init])
{
latency = 1.0;
flags = kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot;
eventStreams = [[NSMutableDictionary alloc] init];
pathReferenceCounts = [[NSMutableDictionary alloc] init];
eventStreamPaths = [[NSCountedSet alloc] init];
}

return self;
Expand All @@ -100,15 +100,15 @@ -(id) init
// * DESTRUCTOR:
// -----------------------------------------------------------------------------

-(void) dealloc
- (void) dealloc
{
[self removeAllPaths];
[eventStreams release];
[pathReferenceCounts release];
[eventStreamPaths release];
[super dealloc];
}

-(void) finalize
- (void) finalize
{
[self removeAllPaths];
[super finalize];
Expand Down Expand Up @@ -159,7 +159,7 @@ - (FSEventStreamCreateFlags) fsEventStreamCreateFlags
// Mutator for file watcher delegate.
// -----------------------------------------------------------------------------

-(void) setDelegate: (id)newDelegate
- (void) setDelegate:(id)newDelegate
{
delegate = newDelegate;
}
Expand All @@ -169,7 +169,7 @@ -(void) setDelegate: (id)newDelegate
// Accessor for file watcher delegate.
// -----------------------------------------------------------------------------

-(id) delegate
- (id) delegate
{
return delegate;
}
Expand All @@ -194,70 +194,79 @@ - (NSString*) pathToParentFolderOfFile:(NSString*)inPath
return inPath;
}

- (BOOL) _registerFSEventsObserverForPath:(NSString*)path
{
BOOL succeeded = YES;
FSEventStreamContext context;
context.version = 0;
context.info = (void*) self;
context.retain = NULL;
context.release = NULL;
context.copyDescription = NULL;

NSArray* pathArray = [NSArray arrayWithObject:path];
FSEventStreamRef stream = FSEventStreamCreate(NULL,&FSEventCallback,&context,(CFArrayRef)pathArray,kFSEventStreamEventIdSinceNow,latency,flags);

if (stream)
{
FSEventStreamScheduleWithRunLoop(stream,CFRunLoopGetMain(),kCFRunLoopCommonModes);
FSEventStreamStart(stream);

[eventStreams setObject:[NSValue valueWithPointer:stream] forKey:path];
}
else
{
NSLog( @"UKFSEventsWatcher _registerFSEventObserverForPath:%@ failed",path);
succeeded = NO;
}

return succeeded;
}

// -----------------------------------------------------------------------------
// addPath:
// Start watching the folder at the specified path.
// Start watching the folder at the specified path, or if we are already watching
// the path, increase its count in eventStreamPaths
// -----------------------------------------------------------------------------

-(void) addPath: (NSString*)path
- (void) addPath:(NSString*)path
{
BOOL succeeded = YES;
path = [self pathToParentFolderOfFile:path];
NSArray* paths = [NSArray arrayWithObject:path];

NSUInteger currentRegistrationCount = 0;

// Do we already have a stream scheduled for this path?
// NOTE: Synchronize the whole thing so we don't run the risk of the current count changing while
// we're busy updating it with our new addition.
@synchronized (self)
{
NSNumber* currentRegistrationCountNumber = [pathReferenceCounts objectForKey:path];
if (currentRegistrationCountNumber != nil)
{
currentRegistrationCount = [currentRegistrationCountNumber unsignedIntValue];
}

BOOL succeeded = YES;

NSUInteger currentRegistrationCount = [eventStreamPaths countForObject:path];
if (currentRegistrationCount == 0)
{
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);

[eventStreams setObject:[NSValue valueWithPointer:stream] forKey:path];
}
else
{
NSLog( @"UKFSEventsWatcher addPath:%@ failed",path);
succeeded = NO;
}
succeeded = [self _registerFSEventsObserverForPath:path];
}

if (succeeded)
{
currentRegistrationCount = currentRegistrationCount + 1;
NSNumber* newCountNumber = [NSNumber numberWithUnsignedInt:currentRegistrationCount];
[pathReferenceCounts setObject:newCountNumber forKey:path];
[eventStreamPaths addObject:path];
}
}
}

- (void) _unregisterFSEventStream:(FSEventStreamRef)stream
{
FSEventStreamStop(stream);
FSEventStreamInvalidate(stream);
FSEventStreamRelease(stream);
}

// -----------------------------------------------------------------------------
// removePath:
// Stop watching the folder at the specified path.
// Decrease the watch count for the given path, and if the count has gone
// to zero, stop watching the given path.
// -----------------------------------------------------------------------------

-(void) removePath: (NSString*)path
- (void) removePath:(NSString*)path
{
// Ensure we are removing a folder, not a file inside the desired folder. This matches
// the normalization done in addPath to make sure removePath for the same path will succeed.
Expand All @@ -267,30 +276,21 @@ -(void) removePath: (NSString*)path

@synchronized (self)
{
NSUInteger currentRegistrationCount = 0;
NSNumber* currentRegistrationCountNumber = [pathReferenceCounts objectForKey:path];
if (currentRegistrationCountNumber != nil)
{
currentRegistrationCount = [currentRegistrationCountNumber unsignedIntValue];
}

// We are sometimes asked to removePath on a path that we were never asked to add. That's
// OK - it just means they are being extra-certain before (probably) adding it for the
// first time...
NSUInteger currentRegistrationCount = [eventStreamPaths countForObject:path];
if (currentRegistrationCount > 0)
{
NSUInteger newRegistrationCount = currentRegistrationCount - 1;
[eventStreamPaths removeObject:path];

NSUInteger newRegistrationCount = [eventStreamPaths countForObject:path];

// Clear everything out if we've gone to zero, otherwise just update with te new count
// Clear everything out if we've gone to zero
if (newRegistrationCount == 0)
{
valueToRemove = [[[eventStreams objectForKey:path] retain] autorelease];
[eventStreams removeObjectForKey:path];
[pathReferenceCounts removeObjectForKey:path];
}
else
{
[pathReferenceCounts setObject:[NSNumber numberWithUnsignedInt:newRegistrationCount] forKey:path];
}
}
}
Expand All @@ -301,35 +301,39 @@ -(void) removePath: (NSString*)path

if (stream)
{
FSEventStreamStop(stream);
FSEventStreamInvalidate(stream);
FSEventStreamRelease(stream);
[self _unregisterFSEventStream:stream];
}
}
}

// -----------------------------------------------------------------------------
// removeAllPaths:
// Stop watching all known folders.
// Stop watching all known paths.
// -----------------------------------------------------------------------------

-(void) removeAllPaths
- (void) removeAllPaths
{
NSEnumerator* paths = [[eventStreams allKeys] objectEnumerator];
NSString* path;

while (path = [paths nextObject])
@synchronized (self)
{
// Kind of a hack, but to get the remove to work as expected, we need
// to make sure the reference count shows up as 1. The client in this
// case is asking us to disregard all reference counts and just remove
// everything ...
@synchronized (self)
// We don't really need the paths, we just need the open FSEventStreamRefs.
// Unregister them all indiscriminately, then remove all objects from
// our tracking collections.

NSEnumerator* eventStreamEnum = [[eventStreams allValues] objectEnumerator];
NSValue* thisEventStreamPointer = nil;

while (thisEventStreamPointer = [eventStreamEnum nextObject])
{
[pathReferenceCounts setObject:[NSNumber numberWithUnsignedInt:1] forKey:path];
FSEventStreamRef stream = [thisEventStreamPointer pointerValue];

if (stream)
{
[self _unregisterFSEventStream:stream];
}
}

[self removePath:path];
[eventStreams removeAllObjects];
[eventStreamPaths removeAllObjects];
}
}

Expand Down

0 comments on commit d9df722

Please sign in to comment.