1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/MYDirectoryWatcher.m Wed Apr 08 16:31:19 2009 -0700
1.3 @@ -0,0 +1,206 @@
1.4 +//
1.5 +// MYDirectoryWatcher.m
1.6 +// Murky
1.7 +//
1.8 +// Copyright 2008 Jens Alfke. All rights reserved.
1.9 +//
1.10 +
1.11 +#import "MYDirectoryWatcher.h"
1.12 +#import <CoreServices/CoreServices.h>
1.13 +
1.14 +
1.15 +static void directoryWatcherCallback(ConstFSEventStreamRef streamRef,
1.16 + void *clientCallBackInfo,
1.17 + size_t numEvents,
1.18 + void *eventPaths,
1.19 + const FSEventStreamEventFlags eventFlags[],
1.20 + const FSEventStreamEventId eventIds[]);
1.21 +
1.22 +@interface MYDirectoryEvent ()
1.23 +- (id) _initWithWatcher: (MYDirectoryWatcher*)itsWatcher
1.24 + path: (NSString*)itsPath
1.25 + flags: (FSEventStreamEventFlags)itsFlags
1.26 + eventID: (FSEventStreamEventId)itsEventID;
1.27 +@end
1.28 +
1.29 +
1.30 +@implementation MYDirectoryWatcher
1.31 +
1.32 +
1.33 +- (id) initWithDirectory: (NSString*)path target: (id)target action: (SEL)action
1.34 +{
1.35 + NSParameterAssert(path);
1.36 + self = [super init];
1.37 + if (self != nil) {
1.38 + _path = path.copy;
1.39 + _target = target;
1.40 + _action = action;
1.41 + _latency = 5.0;
1.42 + _lastEventID = kFSEventStreamEventIdSinceNow;
1.43 + }
1.44 + return self;
1.45 +}
1.46 +
1.47 +- (void) dealloc
1.48 +{
1.49 + [self stop];
1.50 + [_path release];
1.51 + [super dealloc];
1.52 +}
1.53 +
1.54 +- (void) finalize
1.55 +{
1.56 + [self stop];
1.57 + [super finalize];
1.58 +}
1.59 +
1.60 +
1.61 +@synthesize path=_path, latency=_latency, lastEventID=_lastEventID;
1.62 +
1.63 +
1.64 +- (BOOL) start
1.65 +{
1.66 + if( ! _stream ) {
1.67 + FSEventStreamContext context = {0,self,NULL,NULL,NULL};
1.68 + _stream = FSEventStreamCreate(NULL,
1.69 + &directoryWatcherCallback, &context,
1.70 + (CFArrayRef)[NSArray arrayWithObject: _path],
1.71 + _lastEventID,
1.72 + _latency,
1.73 + kFSEventStreamCreateFlagUseCFTypes);
1.74 + if( ! _stream )
1.75 + return NO;
1.76 + FSEventStreamScheduleWithRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
1.77 + if( ! FSEventStreamStart(_stream) ) {
1.78 + [self stop];
1.79 + return NO;
1.80 + }
1.81 + _historyDone = (_lastEventID == kFSEventStreamEventIdSinceNow);
1.82 + Log(@"MYDirectoryWatcher: Started on %@ (latency=%g, lastEvent=%llu)",_path,_latency,_lastEventID);
1.83 + }
1.84 + return YES;
1.85 +}
1.86 +
1.87 +- (void) pause
1.88 +{
1.89 + if( _stream ) {
1.90 + FSEventStreamStop(_stream);
1.91 + FSEventStreamInvalidate(_stream);
1.92 + FSEventStreamRelease(_stream);
1.93 + _stream = NULL;
1.94 + Log(@"MYDirectoryWatcher: Stopped on %@ (lastEvent=%llu)",_path,_lastEventID);
1.95 + }
1.96 +}
1.97 +
1.98 +- (void) stop
1.99 +{
1.100 + [self pause];
1.101 + _lastEventID = kFSEventStreamEventIdSinceNow; // so events from now till next start will be dropped
1.102 + [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(start) object: nil];
1.103 +}
1.104 +
1.105 +- (void) stopTemporarily
1.106 +{
1.107 + if( _stream ) {
1.108 + [self stop];
1.109 + [self performSelector: @selector(start) withObject: nil afterDelay: 0.0];
1.110 + }
1.111 +}
1.112 +
1.113 +
1.114 +- (void) _notifyEvents: (size_t)numEvents
1.115 + paths: (NSArray*)paths
1.116 + flags: (const FSEventStreamEventFlags[])eventFlags
1.117 + eventIDs: (const FSEventStreamEventId[])eventIDs
1.118 +{
1.119 + for (size_t i=0; i<numEvents; i++) {
1.120 + NSString *path = [paths objectAtIndex: i];
1.121 + FSEventStreamEventFlags flags = eventFlags[i];
1.122 + FSEventStreamEventId eventID = eventIDs[i];
1.123 + if( flags & (kFSEventStreamEventFlagMount | kFSEventStreamEventFlagUnmount) ) {
1.124 + if( flags & kFSEventStreamEventFlagMount )
1.125 + Log(@"MYDirectoryWatcher: Volume mounted: %@",path);
1.126 + else
1.127 + Log(@"MYDirectoryWatcher: Volume unmounted: %@",path);
1.128 + } else if( flags & kFSEventStreamEventFlagHistoryDone ) {
1.129 + Log(@"MYDirectoryWatcher: Event #%llu History done",eventID);
1.130 + _historyDone = YES;
1.131 + } else {
1.132 + Log(@"MYDirectoryWatcher: Event #%llu flags=%02x path=%@",eventID,flags,path);
1.133 + if( _historyDone )
1.134 + flags |= kFSEventStreamEventFlagHistoryDone;
1.135 +
1.136 + MYDirectoryEvent *event = [[MYDirectoryEvent alloc] _initWithWatcher: self
1.137 + path: path
1.138 + flags: flags
1.139 + eventID: eventID];
1.140 + [_target performSelector: _action withObject: event];
1.141 + [event release];
1.142 + }
1.143 + _lastEventID = eventIDs[i];
1.144 + }
1.145 +}
1.146 +
1.147 +
1.148 +static void directoryWatcherCallback(ConstFSEventStreamRef streamRef,
1.149 + void *watcher,
1.150 + size_t numEvents,
1.151 + void *eventPaths,
1.152 + const FSEventStreamEventFlags eventFlags[],
1.153 + const FSEventStreamEventId eventIDs[])
1.154 +{
1.155 + [(MYDirectoryWatcher*)watcher _notifyEvents: numEvents
1.156 + paths: (NSArray*)eventPaths
1.157 + flags: eventFlags
1.158 + eventIDs: eventIDs];
1.159 +}
1.160 +
1.161 +
1.162 +
1.163 +@end
1.164 +
1.165 +
1.166 +
1.167 +
1.168 +@implementation MYDirectoryEvent
1.169 +
1.170 +- (id) _initWithWatcher: (MYDirectoryWatcher*)itsWatcher
1.171 + path: (NSString*)itsPath
1.172 + flags: (FSEventStreamEventFlags)itsFlags
1.173 + eventID: (FSEventStreamEventId)itsEventID
1.174 +{
1.175 + self = [super init];
1.176 + if (self != nil) {
1.177 + watcher = itsWatcher;
1.178 + path = itsPath.copy;
1.179 + flags = itsFlags;
1.180 + eventID = itsEventID;
1.181 + }
1.182 + return self;
1.183 +}
1.184 +
1.185 +- (void) dealloc
1.186 +{
1.187 + [path release];
1.188 + [super dealloc];
1.189 +}
1.190 +
1.191 +@synthesize watcher,path,flags,eventID;
1.192 +
1.193 +- (NSString*) relativePath
1.194 +{
1.195 + NSString *base = watcher.path;
1.196 + if( ! [path hasPrefix: base] )
1.197 + return nil;
1.198 + int length = base.length;
1.199 + while( length < path.length && [path characterAtIndex: length]=='/' )
1.200 + length++;
1.201 + return [path substringFromIndex: length];
1.202 +}
1.203 +
1.204 +- (BOOL) mustScanSubdirectories {return (flags & kFSEventStreamEventFlagMustScanSubDirs) != 0;}
1.205 +- (BOOL) eventsWereDropped {return (flags & (kFSEventStreamEventFlagUserDropped|kFSEventStreamEventFlagKernelDropped)) != 0;}
1.206 +- (BOOL) isHistorical {return (flags & kFSEventStreamEventFlagHistoryDone)==0;}
1.207 +- (BOOL) rootChanged {return (flags & kFSEventStreamEventFlagRootChanged)!=0;}
1.208 +
1.209 +@end
1.210 \ No newline at end of file