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