MYDirectoryWatcher.m
author Jens Alfke <jens@mooseyard.com>
Wed May 20 08:34:04 2009 -0700 (2009-05-20)
changeset 32 222393534845
parent 31 2068331949ee
child 35 5cab3034d3a1
permissions -rw-r--r--
Retain/release MYDirectoryWatcher's _standardizedPath, for non-GC compatibility.
     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] copy];
    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     [_standardizedPath release];
    52     [super dealloc];
    53 }
    54 
    55 - (void) finalize
    56 {
    57     [self stop];
    58     [super finalize];
    59 }
    60 
    61 
    62 @synthesize path=_path, latency=_latency, lastEventID=_lastEventID;
    63 
    64 - (NSString*) standardizedPath {
    65     return _standardizedPath;
    66 }
    67 
    68 
    69 - (BOOL) start
    70 {
    71     if( ! _stream ) {
    72         FSEventStreamContext context = {0,self,NULL,NULL,NULL};
    73         _stream = FSEventStreamCreate(NULL, 
    74                                       &directoryWatcherCallback, &context,
    75                                       (CFArrayRef)[NSArray arrayWithObject: _path], 
    76                                       _lastEventID, 
    77                                       _latency, 
    78                                       kFSEventStreamCreateFlagUseCFTypes);
    79         if( ! _stream )
    80             return NO;
    81         FSEventStreamScheduleWithRunLoop(_stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    82         if( ! FSEventStreamStart(_stream) ) {
    83             [self stop];
    84             return NO;
    85         }
    86         _historyDone = (_lastEventID == kFSEventStreamEventIdSinceNow);
    87         LogTo(MYDirectoryWatcher, @"Started on %@ (latency=%g, lastEvent=%llu)",_path,_latency,_lastEventID);
    88     }
    89     return YES;
    90 }
    91 
    92 - (void) pause
    93 {
    94     if( _stream ) {
    95         FSEventStreamStop(_stream);
    96         FSEventStreamInvalidate(_stream);
    97         FSEventStreamRelease(_stream);
    98         _stream = NULL;
    99         LogTo(MYDirectoryWatcher, @"Stopped on %@ (lastEvent=%llu)",_path,_lastEventID);
   100     }
   101 }
   102 
   103 - (void) stop
   104 {
   105     [self pause];
   106     _lastEventID = kFSEventStreamEventIdSinceNow;   // so events from now till next start will be dropped
   107     [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(start) object: nil];
   108 }
   109 
   110 - (void) stopTemporarily
   111 {
   112     if( _stream ) {
   113         [self stop];
   114         [self performSelector: @selector(start) withObject: nil afterDelay: 0.0];
   115     }
   116 }
   117 
   118 
   119 - (void) _notifyEvents: (size_t)numEvents
   120                  paths: (NSArray*)paths
   121                  flags: (const FSEventStreamEventFlags[])eventFlags
   122               eventIDs: (const FSEventStreamEventId[])eventIDs
   123 {
   124     for (size_t i=0; i<numEvents; i++) {
   125         NSString *path = [paths objectAtIndex: i];
   126         FSEventStreamEventFlags flags = eventFlags[i];
   127         FSEventStreamEventId eventID = eventIDs[i];
   128         if( flags & (kFSEventStreamEventFlagMount | kFSEventStreamEventFlagUnmount) ) {
   129             if( flags & kFSEventStreamEventFlagMount )
   130                 LogTo(MYDirectoryWatcher, @"Volume mounted: %@",path);
   131             else
   132                 LogTo(MYDirectoryWatcher, @"Volume unmounted: %@",path);
   133         } else if( flags & kFSEventStreamEventFlagHistoryDone ) {
   134             LogTo(MYDirectoryWatcher, @"Event #%llu History done",eventID);
   135             _historyDone = YES;
   136         } else {
   137             LogTo(MYDirectoryWatcher, @"Event #%llu flags=%02x path=%@",eventID,flags,path);
   138             if( _historyDone )
   139                 flags |= kFSEventStreamEventFlagHistoryDone;
   140             
   141             MYDirectoryEvent *event = [[MYDirectoryEvent alloc] _initWithWatcher: self
   142                                                                         path: path 
   143                                                                        flags: flags
   144                                                                      eventID: eventID];
   145             [_target performSelector: _action withObject: event];
   146             [event release];
   147         }
   148         _lastEventID = eventIDs[i];
   149     }
   150 }
   151 
   152 
   153 static void directoryWatcherCallback(ConstFSEventStreamRef streamRef,
   154                                      void *watcher,
   155                                      size_t numEvents,
   156                                      void *eventPaths,
   157                                      const FSEventStreamEventFlags eventFlags[],
   158                                      const FSEventStreamEventId eventIDs[])
   159 {
   160     [(MYDirectoryWatcher*)watcher _notifyEvents: numEvents
   161                                           paths: (NSArray*)eventPaths
   162                                           flags: eventFlags
   163                                        eventIDs: eventIDs];
   164 }
   165 
   166 
   167 
   168 @end
   169 
   170 
   171 
   172 
   173 @implementation MYDirectoryEvent
   174 
   175 - (id) _initWithWatcher: (MYDirectoryWatcher*)itsWatcher
   176                    path: (NSString*)itsPath 
   177                   flags: (FSEventStreamEventFlags)itsFlags
   178                 eventID: (FSEventStreamEventId)itsEventID
   179 {
   180     self = [super init];
   181     if (self != nil) {
   182         watcher = itsWatcher;
   183         path = itsPath.copy;
   184         flags = itsFlags;
   185         eventID = itsEventID;
   186     }
   187     return self;
   188 }
   189 
   190 - (void) dealloc
   191 {
   192     [path release];
   193     [super dealloc];
   194 }
   195 
   196 @synthesize watcher,path,flags,eventID;
   197 
   198 - (NSString*) relativePath
   199 {
   200     NSString *base = watcher.standardizedPath;
   201     NSString *standardizedPath = [path stringByStandardizingPath];
   202     if( ! [standardizedPath hasPrefix: base] )
   203         return nil;
   204     unsigned length = base.length;
   205     while( length < standardizedPath.length && [standardizedPath characterAtIndex: length]=='/' )
   206         length++;
   207     return [standardizedPath substringFromIndex: length];
   208 }
   209 
   210 - (BOOL) mustScanSubdirectories     {return (flags & kFSEventStreamEventFlagMustScanSubDirs) != 0;}
   211 - (BOOL) eventsWereDropped          {return (flags & (kFSEventStreamEventFlagUserDropped|kFSEventStreamEventFlagKernelDropped)) != 0;}
   212 - (BOOL) isHistorical               {return (flags & kFSEventStreamEventFlagHistoryDone)==0;}
   213 - (BOOL) rootChanged                {return (flags & kFSEventStreamEventFlagRootChanged)!=0;}
   214 
   215 @end