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