-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathSGDirWatchdog.m
158 lines (127 loc) · 4.49 KB
/
SGDirWatchdog.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//
// SGDirObserver.m
// DirectoryObserver
//
// Copyright (c) 2011 Simon Grätzer.
//
#import "SGDirWatchdog.h"
#import <fcntl.h>
#import <unistd.h>
#import <sys/event.h>
@interface SGDirWatchdog ()
@property (nonatomic, readonly) CFFileDescriptorRef kqRef;
- (void)kqueueFired;
@end
static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info) {
// Pick up the object passed in the "info" member of the CFFileDescriptorContext passed to CFFileDescriptorCreate
SGDirWatchdog* obj = (__bridge SGDirWatchdog*) info;
if ([obj isKindOfClass:[SGDirWatchdog class]] && // If we can call back to the proper sort of object ...
(kqRef == obj.kqRef) && // and the FD that issued the CB is the expected one ...
(callBackTypes == kCFFileDescriptorReadCallBack) ) // and we're processing the proper sort of CB ...
{
[obj kqueueFired]; // Invoke the instance's CB handler
}
}
@implementation SGDirWatchdog {
int _dirFD;
CFFileDescriptorRef _kqRef;
}
+ (NSString *)documentsPath {
NSArray *documentsPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return documentsPaths[0]; // Path to the application's "Documents" directory
}
+ (id)watchtdogOnDocumentsDir:(void (^)(void))update; {
return [[SGDirWatchdog alloc]initWithPath:[self documentsPath] update:update];
}
- (id)initWithPath:(NSString *)path update:(void (^)(void))update; {
if ((self = [super init])) {
_path = path;
_update = [update copy];
}
return self;
}
- (void)dealloc {
[self stop];
}
#pragma mark -
#pragma mark Extension methods
- (void)kqueueFired {
// Pull the native FD around which the CFFileDescriptor was wrapped
int kq = CFFileDescriptorGetNativeDescriptor(_kqRef);
if (kq < 0) return;
// If we pull a single available event out of the queue, assume the directory was updated
struct kevent event;
struct timespec timeout = {0, 0};
if (kevent(kq, NULL, 0, &event, 1, &timeout) == 1 && _update) {
_update();
}
// (Re-)Enable a one-shot (the only kind) callback
CFFileDescriptorEnableCallBacks(_kqRef, kCFFileDescriptorReadCallBack);
}
- (void)start {
// One ping only
if (_kqRef != NULL) return;
// Fetch pathname of the directory to monitor
NSString* docPath = self.path;
if (!docPath) return;
// Open an event-only file descriptor associated with the directory
int dirFD = open([docPath fileSystemRepresentation], O_EVTONLY);
if (dirFD < 0) return;
// Create a new kernel event queue
int kq = kqueue();
if (kq < 0)
{
close(dirFD);
return;
}
// Set up a kevent to monitor
struct kevent eventToAdd; // Register an (ident, filter) pair with the kqueue
eventToAdd.ident = dirFD; // The object to watch (the directory FD)
eventToAdd.filter = EVFILT_VNODE; // Watch for certain events on the VNODE spec'd by ident
eventToAdd.flags = EV_ADD | EV_CLEAR; // Add a resetting kevent
eventToAdd.fflags = NOTE_WRITE; // The events to watch for on the VNODE spec'd by ident (writes)
eventToAdd.data = 0; // No filter-specific data
eventToAdd.udata = NULL; // No user data
// Add a kevent to monitor
if (kevent(kq, &eventToAdd, 1, NULL, 0, NULL)) {
close(kq);
close(dirFD);
return;
}
// Wrap a CFFileDescriptor around a native FD
CFFileDescriptorContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
_kqRef = CFFileDescriptorCreate(NULL, // Use the default allocator
kq, // Wrap the kqueue
true, // Close the CFFileDescriptor if kq is invalidated
KQCallback, // Fxn to call on activity
&context); // Supply a context to set the callback's "info" argument
if (_kqRef == NULL) {
close(kq);
close(dirFD);
return;
}
// Spin out a pluggable run loop source from the CFFileDescriptorRef
// Add it to the current run loop, then release it
CFRunLoopSourceRef rls = CFFileDescriptorCreateRunLoopSource(NULL, _kqRef, 0);
if (rls == NULL) {
CFRelease(_kqRef); _kqRef = NULL;
close(kq);
close(dirFD);
return;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
// Store the directory FD for later closing
_dirFD = dirFD;
// Enable a one-shot (the only kind) callback
CFFileDescriptorEnableCallBacks(_kqRef, kCFFileDescriptorReadCallBack);
}
- (void)stop {
if (_kqRef) {
close(_dirFD);
CFFileDescriptorInvalidate(_kqRef);
CFRelease(_kqRef);
_kqRef = NULL;
}
}
@end