Vuo  2.4.0
VuoFileWatcher.cc
Go to the documentation of this file.
1
10#include <dirent.h>
11#include "VuoFileWatcher.hh"
12
13const double VuoFileWatcher::checkIntervalSeconds = 2;
14const double VuoFileWatcher::changeNotificationDelaySeconds = 0.25;
15
30VuoFileWatcher::VuoFileWatcher(VuoFileWatcherDelegate *delegate, const string &fileOrFolderToWatch, bool persistent)
31{
32 queue = dispatch_queue_create("org.vuo.filewatcher", NULL);
33 dispatch_sync(queue, ^{
34 initialize(delegate, fileOrFolderToWatch, persistent, NULL);
35 });
36}
37
41VuoFileWatcher::VuoFileWatcher(VuoFileWatcherDelegate *delegate, const string &fileOrFolderToWatch, bool persistent, VuoFileWatcher *parent)
42{
43 queue = parent->queue;
44 initialize(delegate, fileOrFolderToWatch, persistent, parent);
45}
46
52void VuoFileWatcher::initialize(VuoFileWatcherDelegate *delegate, const string &fileOrFolderToWatch, bool persistent, VuoFileWatcher *parent)
53{
54 this->parent = parent;
55 this->fileToWatch = fileOrFolderToWatch;
56 this->delegate = delegate;
57 this->persistent = persistent;
58 this->fileToWatchSource = NULL;
59 this->fileToWatchSourceRunning = dispatch_group_create();
60 this->reopen = false;
61 this->periodicCheckForExistence = NULL;
62 this->periodicCheckForExistenceRunning = dispatch_group_create();
63 this->childWatchersQueue = dispatch_queue_create("org.vuo.filewatcher.child", NULL);
64 this->changeNotificationDelay = NULL;
65 this->changeNotificationDelayRunning = dispatch_group_create();
66
67 DIR *d = opendir(fileOrFolderToWatch.c_str());
68 if (d)
69 {
70 closedir(d);
71 startWatching();
72 }
73 else if (errno == ENOTDIR)
74 {
75 startWatching();
76 }
77 else if (errno == ENOENT)
78 {
79 if (persistent)
80 periodicallyCheckForExistence();
81 }
82 else
83 {
84 VUserLog("Error: Couldn't access '%s': %s", fileOrFolderToWatch.c_str(), strerror(errno));
85 }
86}
87
93void VuoFileWatcher::startWatching()
94{
95 fileToWatchFD = open(fileToWatch.c_str(), O_EVTONLY);
96 if (fileToWatchFD < 0)
97 {
98 VUserLog("Error: %s", strerror(errno));
99 return;
100 }
101
102 dispatch_group_enter(fileToWatchSourceRunning);
103
104 unsigned long mask = DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_WRITE;
105 fileToWatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileToWatchFD, mask, queue);
106 dispatch_source_set_event_handler(fileToWatchSource, ^{
107 unsigned long l = dispatch_source_get_data(fileToWatchSource);
108
109 if (l & DISPATCH_VNODE_DELETE
110 || l & DISPATCH_VNODE_RENAME)
111 {
112 DIR *d = opendir(fileToWatch.c_str());
113 if (d)
114 {
115 // got a delete event, but it still exists and is a folder — not sure how this could happen
116 closedir(d);
117 }
118 else if (errno == ENOTDIR)
119 {
120 changeObserved();
121 reopen = true;
122 dispatch_source_cancel(fileToWatchSource);
123 }
124 else if (errno == ENOENT)
125 {
126 changeObserved();
127 dispatch_source_cancel(fileToWatchSource);
128 if (persistent)
129 periodicallyCheckForExistence();
130 }
131 }
132 else if (l & DISPATCH_VNODE_WRITE)
133 {
134 changeObserved();
135 updateChildWatchers();
136 }
137 });
138 dispatch_source_set_cancel_handler(fileToWatchSource, ^{
139 close(fileToWatchFD);
140
141 dispatch_release(fileToWatchSource);
142 fileToWatchSource = NULL;
143 dispatch_group_leave(fileToWatchSourceRunning);
144
145 if (reopen)
146 {
147 reopen = false;
148 startWatching();
149 }
150 });
151 dispatch_resume(fileToWatchSource);
152
153 updateChildWatchers();
154}
155
162void VuoFileWatcher::periodicallyCheckForExistence()
163{
164 dispatch_group_enter(periodicCheckForExistenceRunning);
165
166 periodicCheckForExistence = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
167 dispatch_source_set_timer(periodicCheckForExistence, dispatch_time(DISPATCH_TIME_NOW, checkIntervalSeconds*NSEC_PER_SEC), checkIntervalSeconds*NSEC_PER_SEC, checkIntervalSeconds*NSEC_PER_SEC/10);
168 dispatch_source_set_event_handler(periodicCheckForExistence, ^{
169 DIR *d = opendir(fileToWatch.c_str());
170 if (d)
171 {
172 closedir(d);
173 changeObserved();
174 startWatching();
175 dispatch_source_cancel(periodicCheckForExistence);
176 }
177 });
178 dispatch_source_set_cancel_handler(periodicCheckForExistence, ^{
179 dispatch_release(periodicCheckForExistence);
180 periodicCheckForExistence = NULL;
181 dispatch_group_leave(periodicCheckForExistenceRunning);
182 });
183 dispatch_resume(periodicCheckForExistence);
184}
185
193void VuoFileWatcher::updateChildWatchers()
194{
195 DIR *d = opendir(fileToWatch.c_str());
196 if (!d)
197 return;
198
199 __block vector<VuoFileWatcher *> childWatchersToDestroy;
200 dispatch_sync(childWatchersQueue, ^{
201
202 // Add new child watchers for new files/folders.
203 struct dirent *de;
204 while ((de = readdir(d)) != NULL)
205 {
206 if (strcmp(de->d_name, ".") == 0
207 || strcmp(de->d_name, "..") == 0)
208 continue;
209
210 string compositePath = fileToWatch + '/' + de->d_name;
211
212 bool haveExistingWatcher = false;
213 for (vector<VuoFileWatcher *>::iterator i = childWatchers.begin(); i != childWatchers.end(); ++i)
214 if ((*i)->fileToWatch == compositePath)
215 {
216 haveExistingWatcher = true;
217 break;
218 }
219
220 if (haveExistingWatcher)
221 continue;
222
223 childWatchers.push_back(new VuoFileWatcher(delegate, compositePath, false, this));
224 }
225
226 // Remove obsolete child watchers.
227 for (vector<VuoFileWatcher *>::iterator i = childWatchers.begin(); i != childWatchers.end(); )
228 {
229 if (!(*i)->fileToWatchSource
230 && !(*i)->periodicCheckForExistence)
231 {
232 childWatchersToDestroy.push_back(*i);
233 i = childWatchers.erase(i);
234 }
235 else
236 ++i;
237 }
238
239 });
240
241 for (VuoFileWatcher *childWatcher : childWatchersToDestroy)
242 delete childWatcher;
243
244 closedir(d);
245}
246
252void VuoFileWatcher::changeObserved()
253{
254 if (parent)
255 {
256 parent->changeObserved();
257 return;
258 }
259
260 if (changeNotificationDelay)
261 // Push the timer back.
262 dispatch_source_set_timer(changeNotificationDelay, dispatch_time(DISPATCH_TIME_NOW, changeNotificationDelaySeconds*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, changeNotificationDelaySeconds*NSEC_PER_SEC/10);
263 else
264 {
265 dispatch_group_enter(changeNotificationDelayRunning);
266
267 changeNotificationDelay = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
268 dispatch_source_set_timer(changeNotificationDelay, dispatch_time(DISPATCH_TIME_NOW, changeNotificationDelaySeconds*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, changeNotificationDelaySeconds*NSEC_PER_SEC/10);
269 dispatch_source_set_event_handler(changeNotificationDelay, ^{
270 delegate->fileChanged(fileToWatch);
271 dispatch_source_cancel(changeNotificationDelay);
272 });
273 dispatch_source_set_cancel_handler(changeNotificationDelay, ^{
274 dispatch_release(changeNotificationDelay);
275 changeNotificationDelay = NULL;
276 dispatch_group_leave(changeNotificationDelayRunning);
277 });
278 dispatch_resume(changeNotificationDelay);
279 }
280}
281
286{
287 __block vector<VuoFileWatcher *> childWatchersCopy;
288 dispatch_sync(childWatchersQueue, ^{
289 childWatchersCopy = childWatchers;
290 childWatchers.clear();
291 });
292
293 for (VuoFileWatcher *childWatcher : childWatchersCopy)
294 delete childWatcher;
295
296 if (fileToWatchSource)
297 {
298 dispatch_source_cancel(fileToWatchSource);
299 dispatch_group_wait(fileToWatchSourceRunning, DISPATCH_TIME_FOREVER);
300 }
301 dispatch_release(fileToWatchSourceRunning);
302
303 if (periodicCheckForExistence)
304 {
305 dispatch_source_cancel(periodicCheckForExistence);
306 dispatch_group_wait(periodicCheckForExistenceRunning, DISPATCH_TIME_FOREVER);
307 }
308 dispatch_release(periodicCheckForExistenceRunning);
309
310 if (changeNotificationDelay)
311 {
312 dispatch_source_cancel(changeNotificationDelay);
313 dispatch_group_wait(changeNotificationDelayRunning, DISPATCH_TIME_FOREVER);
314 }
315 dispatch_release(changeNotificationDelayRunning);
316
317 if (! parent)
318 dispatch_release(queue);
319
320 dispatch_release(childWatchersQueue);
321}