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