MYDirectoryWatcher.m
author Jens Alfke <jens@mooseyard.com>
Thu May 14 20:44:32 2009 -0700 (2009-05-14)
changeset 31 2068331949ee
parent 30 2befbe36c746
child 32 222393534845
permissions -rw-r--r--
* Optimized Olivier's MYDirectoryWatcher fix (by caching the watcher's standardized path)
* Added -[NSData my_UTF8ToString] to CollectionUtils.
     1 //
     2 //  MYDirectoryWatcher.m
     3 //  Murky
     4 //
     5 //  Copyright 2008 Jens Alfke. All rights reserved.
     6 //
     7 
     8 #import "MYDirectoryWatcher.h"
     9 #import "Test.h"
    10 #import "Logging.h"
    11 #import <CoreServices/CoreServices.h>
    12 
    13 
    14 static void directoryWatcherCallback(ConstFSEventStreamRef streamRef,
    15                                      void *clientCallBackInfo,
    16                                      size_t numEvents,
    17                                      void *eventPaths,
    18                                      const FSEventStreamEventFlags eventFlags[],
    19                                      const FSEventStreamEventId eventIds[]);
    20 
    21 @interface MYDirectoryEvent ()
    22 - (id) _initWithWatcher: (MYDirectoryWatcher*)itsWatcher
    23                    path: (NSString*)itsPath 
    24                   flags: (FSEventStreamEventFlags)itsFlags
    25                 eventID: (FSEventStreamEventId)itsEventID;
    26 @end
    27 
    28 
    29 @implementation MYDirectoryWatcher
    30 
    31 
    32 - (id) initWithDirectory: (NSString*)path target: (id)target action: (SEL)action
    33 {
    34     Assert(path!=nil);
    35     self = [super init];
    36     if (self != nil) {
    37         _path = path.copy;
    38         _standardizedPath = [_path stringByStandardizingPath];
    39         _target = target;
    40         _action = action;
    41         _latency = 5.0;
    42         _lastEventID = kFSEventStreamEventIdSinceNow;
    43     }
    44     return self;
    45 }
    46 
    47 - (void) dealloc
    48 {
    49     [self stop];
    50     [_path release];
    51     [super dealloc];
    52 }
    53 
    54 - (void) finalize
    55 {
    56     [self stop];
    57     [super finalize];
    58 }
    59 
    60 
    61 @synthesize path=_path, latency=_latency, lastEventID=_lastEventID;
    62 
    63 - (NSString*) standardizedPath {
    64     return _standardizedPath;
    65 }
    66 
    67 
    68 - (BOOL) start
    69 {
    70     if( ! _stream ) {
    71         FSEventStreamContext context = {0,self,NULL,NULL,NULL};
    72         _stream = FSEventStreamCreate(NULL, 
    73                                       &directoryWatcherCallback, &context,
    74                                       (CFArrayRef)[NSArray arrayWithObject: _path], 
    75                                       _lastEventID, 
    76                                       _latency, 
    77                                       kFSEventStreamCreateFlagUseCFTypes);
    78         if( ! _stream )
    79             return NO;
    80         FSEventStreamScheduleWithRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    81         if( ! FSEventStreamStart(_stream) ) {
    82             [self stop];
    83             return NO;
    84         }
    85         _historyDone = (_lastEventID == kFSEventStreamEventIdSinceNow);
    86         LogTo(MYDirectoryWatcher, @"Started on %@ (latency=%g, lastEvent=%llu)",_path,_latency,_lastEventID);
    87     }
    88     return YES;
    89 }
    90 
    91 - (void) pause
    92 {
    93     if( _stream ) {
    94         FSEventStreamStop(_stream);
    95         FSEventStreamInvalidate(_stream);
    96         FSEventStreamRelease(_stream);
    97         _stream = NULL;
    98         LogTo(MYDirectoryWatcher, @"Stopped on %@ (lastEvent=%llu)",_path,_lastEventID);
    99     }
   100 }
   101 
   102 - (void) stop
   103 {
   104     [self pause];
   105     _lastEventID = kFSEventStreamEventIdSinceNow;   // so events from now till next start will be dropped
   106     [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(start) object: nil];
   107 }
   108 
   109 - (void) stopTemporarily
   110 {
   111     if( _stream ) {
   112         [self stop];
   113         [self performSelector: @selector(start) withObject: nil afterDelay: 0.0];
   114     }
   115 }
   116 
   117 
   118 - (void) _notifyEvents: (size_t)numEvents
   119                  paths: (NSArray*)paths
   120                  flags: (const FSEventStreamEventFlags[])eventFlags
   121               eventIDs: (const FSEventStreamEventId[])eventIDs
   122 {
   123     for (size_t i=0; i<numEvents; i++) {
   124         NSString *path = [paths objectAtIndex: i];
   125         FSEventStreamEventFlags flags = eventFlags[i];
   126         FSEventStreamEventId eventID = eventIDs[i];
   127         if( flags & (kFSEventStreamEventFlagMount | kFSEventStreamEventFlagUnmount) ) {
   128             if( flags & kFSEventStreamEventFlagMount )
   129                 LogTo(MYDirectoryWatcher, @"Volume mounted: %@",path);
   130             else
   131                 LogTo(MYDirectoryWatcher, @"Volume unmounted: %@",path);
   132         } else if( flags & kFSEventStreamEventFlagHistoryDone ) {
   133             LogTo(MYDirectoryWatcher, @"Event #%llu History done",eventID);
   134             _historyDone = YES;
   135         } else {
   136             LogTo(MYDirectoryWatcher, @"Event #%llu flags=%02x path=%@",eventID,flags,path);
   137             if( _historyDone )
   138                 flags |= kFSEventStreamEventFlagHistoryDone;
   139             
   140             MYDirectoryEvent *event = [[MYDirectoryEvent alloc] _initWithWatcher: self
   141                                                                         path: path 
   142                                                                        flags: flags
   143                                                                      eventID: eventID];
   144             [_target performSelector: _action withObject: event];
   145             [event release];
   146         }
   147         _lastEventID = eventIDs[i];
   148     }
   149 }
   150 
   151 
   152 static void directoryWatcherCallback(ConstFSEventStreamRef streamRef,
   153                                      void *watcher,
   154                                      size_t numEvents,
   155                                      void *eventPaths,
   156                                      const FSEventStreamEventFlags eventFlags[],
   157                                      const FSEventStreamEventId eventIDs[])
   158 {
   159     [(MYDirectoryWatcher*)watcher _notifyEvents: numEvents
   160                                           paths: (NSArray*)eventPaths
   161                                           flags: eventFlags
   162                                        eventIDs: eventIDs];
   163 }
   164 
   165 
   166 
   167 @end
   168 
   169 
   170 
   171 
   172 @implementation MYDirectoryEvent
   173 
   174 - (id) _initWithWatcher: (MYDirectoryWatcher*)itsWatcher
   175                    path: (NSString*)itsPath 
   176                   flags: (FSEventStreamEventFlags)itsFlags
   177                 eventID: (FSEventStreamEventId)itsEventID
   178 {
   179     self = [super init];
   180     if (self != nil) {
   181         watcher = itsWatcher;
   182         path = itsPath.copy;
   183         flags = itsFlags;
   184         eventID = itsEventID;
   185     }
   186     return self;
   187 }
   188 
   189 - (void) dealloc
   190 {
   191     [path release];
   192     [super dealloc];
   193 }
   194 
   195 @synthesize watcher,path,flags,eventID;
   196 
   197 - (NSString*) relativePath
   198 {
   199     NSString *base = watcher.standardizedPath;
   200     NSString *standardizedPath = [path stringByStandardizingPath];
   201     if( ! [standardizedPath hasPrefix: base] )
   202         return nil;
   203     unsigned length = base.length;
   204     while( length < standardizedPath.length && [standardizedPath characterAtIndex: length]=='/' )
   205         length++;
   206     return [standardizedPath substringFromIndex: length];
   207 }
   208 
   209 - (BOOL) mustScanSubdirectories     {return (flags & kFSEventStreamEventFlagMustScanSubDirs) != 0;}
   210 - (BOOL) eventsWereDropped          {return (flags & (kFSEventStreamEventFlagUserDropped|kFSEventStreamEventFlagKernelDropped)) != 0;}
   211 - (BOOL) isHistorical               {return (flags & kFSEventStreamEventFlagHistoryDone)==0;}
   212 - (BOOL) rootChanged                {return (flags & kFSEventStreamEventFlagRootChanged)!=0;}
   213 
   214 @end