MYDirectoryWatcher.m
changeset 24 629c7605ab2a
child 27 256370e8935a
     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