Vuo  2.2.1
VuoFileWatcher.cc
Go to the documentation of this file.
1 
10 #include <dirent.h>
11 #include "VuoFileWatcher.hh"
12 
13 const double VuoFileWatcher::checkIntervalSeconds = 2;
14 const double VuoFileWatcher::changeNotificationDelaySeconds = 0.25;
15 
30 VuoFileWatcher::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 
41 VuoFileWatcher::VuoFileWatcher(VuoFileWatcherDelegate *delegate, const string &fileOrFolderToWatch, bool persistent, VuoFileWatcher *parent)
42 {
43  queue = parent->queue;
44  initialize(delegate, fileOrFolderToWatch, persistent, parent);
45 }
46 
52 void 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 
93 void 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 
162 void 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 
193 void 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 
252 void 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 }