rhino/demo/c/dr_libs/old/dr_fsw.h
Go to the documentation of this file.
1 // Public Domain. See "unlicense" statement at the end of this file.
2 
3 // ABOUT
4 //
5 // dr_fsw is a simple library for watching for changes to the file system. This is not a full-featured library
6 // and is only intended for basic use cases.
7 //
8 // Limitations:
9 // - Only Windows is supported at the moment.
10 //
11 //
12 //
13 // USAGE
14 //
15 // This is a single-file library. To use it, do something like the following in one .c file.
16 // #define DR_FSW_IMPLEMENTATION
17 // #include "dr_fsw.h"
18 //
19 // You can then #include this file in other parts of the program as you would with any other header file.
20 //
21 // Example:
22 // // Startup.
23 // drfsw_context* context = drfsw_create_context();
24 // if (context == NULL)
25 // {
26 // // There was an error creating the context...
27 // }
28 //
29 // drfsw_add_directory(context, "C:/My/Folder");
30 // drfsw_add_directory(context, "C:/My/Other/Folder");
31 //
32 // ...
33 //
34 // // Meanwhile, in another thread...
35 // void MyOtherThreadEntryProc()
36 // {
37 // drfsw_event e;
38 // while (isMyApplicationStillAlive && drfsw_next_event(context, &e))
39 // {
40 // switch (e.type)
41 // {
42 // case drfsw_event_type_created: OnFileCreated(e.absolutePath); break;
43 // case drfsw_event_type_deleted: OnFileDeleted(e.absolutePath); break;
44 // case drfsw_event_type_renamed: OnFileRenamed(e.absolutePath, e.absolutePathNew); break;
45 // case drfsw_event_type_updated: OnFileUpdated(e.absolutePath); break;
46 // default: break;
47 // }
48 // }
49 // }
50 //
51 // ...
52 //
53 // // Shutdown
54 // drfsw_context* contextOld = context;
55 // context = NULL;
56 // drfsw_delete_context(contextOld);
57 //
58 // Directories are watched recursively, so try to avoid using this on high level directories
59 // like "C:\". Also avoid watching a directory that is a descendant of another directory that's
60 // already being watched.
61 //
62 // It does not matter whether or not paths are specified with forward or back slashes; the
63 // library will normalize all of that internally depending on the platform. Note, however,
64 // that events always report their paths with forward slashes.
65 //
66 // You don't need to wait for events on a separate thread, however drfsw_next_event() is
67 // a blocking call, so it's usually best to do so. Alternatively, you can use
68 // drfsw_peek_event() which is the same, except non-blocking. Avoid using both
69 // drfsw_next_event() and drfsw_peek_event() at the same time because both will remove
70 // the event from the internal queue so it likely won't work the way you expect.
71 //
72 // The shutdown sequence is a bit strange, but since another thread is accessing that pointer,
73 // you should set any shared pointers to null before calling drfsw_delete_context(). That way,
74 // the next call to drfsw_next_event() will pass in a null pointer which will cause it to
75 // immediately return with zero and thus break the loop and terminate the thread. It is up to
76 // the application to ensure the pointer passed to drfsw_next_event() is valid. Deleting a context
77 // will cause a waiting call to drfsw_next_event() to return 0, however there is a chance that the
78 // context is deleted before drfsw_next_event() has entered into it's wait state. It's up to the
79 // application to make sure the context remains valid.
80 //
81 //
82 //
83 // QUICK NOTES
84 // - Files that are not on the machine's local file system will not be detected (such as files on a network drive).
85 // - In some cases, renaming files won't be detected. Instead it may be implemented as a delete/create pair.
86 //
87 // - Win32: Every directory that is watched becomes "in use" by the operating system. It is still possible
88 // to modify the files and folders inside the watched directory, however.
89 // - Win32: There is a known issue with the ReadDirectoryChangesW() watch technique (which is used internally)
90 // where some events won't get processed if a large number of files change in a short period of time.
91 
92 #ifndef dr_fsw_h
93 #define dr_fsw_h
94 
95 #ifdef __cplusplus
96 extern "C" {
97 #endif
98 
99 // The maximum length of a path in bytes, including the null terminator. If a path exceeds this amount, it will be set to an empty
100 // string. When this is changed the source file will need to be recompiled. Most of the time leaving this at 256 is fine, but it's
101 // not a problem to increase the size if you are encountering issues. Note that increasing this value will increase memory usage
102 // on both the heap and the stack.
103 #ifndef DRFSW_MAX_PATH
104 //#define DRFSW_MAX_PATH 256U
105 #define DRFSW_MAX_PATH 1024U
106 //#define DRFSW_MAX_PATH 4096U
107 #endif
108 
109 // The maximum size of the event queue before it overflows.
110 #define DRFSW_EVENT_QUEUE_SIZE 1024U
111 
112 
113 // The different event types.
114 typedef enum
115 {
120 
122 
123 
124 // Structure containing information about an event.
125 typedef struct
126 {
127  // The type of the event: created, deleted, renamed or updated.
128  drfsw_event_type type;
129 
130  // The absolute path of the file. For renamed events, this is the old name.
131  char absolutePath[DRFSW_MAX_PATH];
132 
133  // The new file name. This is only used for renamed events. For other event types, this will be an empty string.
134  char absolutePathNew[DRFSW_MAX_PATH];
135 
136  // The absolute base path. For renamed events, this is the old base path.
137  char absoluteBasePath[DRFSW_MAX_PATH];
138 
139  // The absolute base path for the new file name. This is only used for renamed events. For other event types, this will be an empty string.
140  char absoluteBasePathNew[DRFSW_MAX_PATH];
141 
142 } drfsw_event;
143 
144 
145 typedef void* drfsw_context;
146 
147 
148 // Creates a file system watcher.
149 //
150 // This will create a background thread that will do the actual checking.
152 
153 // Deletes the given file system watcher.
154 //
155 // This will not return until the thread watching for changes has returned.
156 //
157 // You do not need to remove the watched directories beforehand - this function will make sure everything is cleaned up properly.
158 void drfsw_delete_context(drfsw_context* pContext);
159 
160 
161 // Adds a directory to watch. This will watch for files and folders recursively.
162 int drfsw_add_directory(drfsw_context* pContext, const char* absolutePath);
163 
164 // Removes a watched directory.
165 void drfsw_remove_directory(drfsw_context* pContext, const char* absolutePath);
166 
167 // Helper for removing every watched directory.
169 
170 // Determines whether or not the given directory is being watched.
171 int drfsw_is_watching_directory(drfsw_context* pContext, const char* absolutePath);
172 
173 
174 // Waits for an event from the file system.
175 //
176 // This is a blocking function. Call drfsw_peek_event() to do a non-blocking call. If an error occurs, or the context is deleted, 0
177 // will be returned and the memory pointed to by pEventOut will be undefined.
178 //
179 // This can be called from any thread, however it should not be called from multiple threads simultaneously.
180 //
181 // Use caution when using this combined with drfsw_peek_event(). In almost all cases you should use just one or the other at any
182 // given time.
183 //
184 // It is up to the application to ensure the context is still valid before calling this function.
185 //
186 // Example Usage:
187 //
188 // void MyFSWatcher() {
189 // drfsw_event e;
190 // while (isMyContextStillAlive && drfsw_next_event(context, e)) {
191 // // Do something with the event...
192 // }
193 // }
194 int drfsw_next_event(drfsw_context* pContext, drfsw_event* pEventOut);
195 
196 // Checks to see if there is a pending event, and if so, returns non-zero and fills the given structure with the event details. This
197 // removes the event from the queue.
198 //
199 // This can be called from any thread, however it should not be called from multiple threads simultaneously.
200 //
201 // It is up to the application to ensure the context is still valid before calling this function.
202 int drfsw_peek_event(drfsw_context* pContext, drfsw_event* pEventOut);
203 
204 
205 #ifdef __cplusplus
206 }
207 #endif
208 
209 #endif //dr_fsw_h
210 
211 
213 //
214 // IMPLEMENTATION
215 //
217 #ifdef DR_FSW_IMPLEMENTATION
218 
219 // NOTES:
220 //
221 // Win32 and ReadDirectoryChangesW
222 //
223 // Here is how watching for changes via the ReadDirectoryChangesW() works:
224 // 1) You create a handle to the directory with CreateFile()
225 // 2) You pass this handle to ReadDirectoryChangesW(), including a pointer to a function that is called when changes to the directory are made.
226 // 3) From the aforementioned callback, ReadDirectoryChangesW() needs to be called again
227 //
228 // There are, however, a lot of details that need to be handled correctly in order for this to work
229 //
230 // First of all, the callback passed to ReadDirectoryChangesW() will not be called unless the calling thread is in an alertable state. A thread
231 // is put into an alertable state with WaitForMultipleObjectsEx() (the Ex version is important since it has an extra parameter that lets you
232 // put the thread into an alertable state). Using this blocks the thread which means you need to create a worker thread in the background.
233 
234 #if defined(__clang__)
235  #pragma GCC diagnostic push
236  #pragma GCC diagnostic ignored "-Wcast-align"
237 #endif
238 
239 #ifndef DRFSW_PRIVATE
240 #define DRFSW_PRIVATE static
241 #endif
242 
243 // The number of FILE_NOTIFY_INFORMATION structures in the buffer that's passed to ReadDirectoryChangesW()
244 #define WIN32_RDC_FNI_COUNT DRFSW_EVENT_QUEUE_SIZE
245 
246 #include <assert.h>
247 
248 #if defined(_WIN32)
249 #include <windows.h>
250 
251 DRFSW_PRIVATE void* drfsw_malloc(size_t sizeInBytes)
252 {
253  return HeapAlloc(GetProcessHeap(), 0, sizeInBytes);
254 }
255 
256 DRFSW_PRIVATE void drfsw_free(void* p)
257 {
258  HeapFree(GetProcessHeap(), 0, p);
259 }
260 
261 DRFSW_PRIVATE void drfsw_memcpy(void* dst, const void* src, size_t sizeInBytes)
262 {
263  CopyMemory(dst, src, sizeInBytes);
264 }
265 
266 DRFSW_PRIVATE void drfsw_zeromemory(void* dst, size_t sizeInBytes)
267 {
268  ZeroMemory(dst, sizeInBytes);
269 }
270 #else
271 #include <stdlib.h>
272 #include <string.h>
273 
274 DRFSW_PRIVATE void* drfsw_malloc(size_t sizeInBytes)
275 {
276  return malloc(sizeInBytes);
277 }
278 
279 DRFSW_PRIVATE void drfsw_free(void* p)
280 {
281  free(p);
282 }
283 
284 DRFSW_PRIVATE void drfsw_memcpy(void* dst, const void* src, size_t sizeInBytes)
285 {
286  memcpy(dst, src, sizeInBytes);
287 }
288 
289 DRFSW_PRIVATE void drfsw_zeromemory(void* dst, size_t sizeInBytes)
290 {
291  memset(dst, 0, sizeInBytes);
292 }
293 #endif
294 
295 
296 DRFSW_PRIVATE int drfsw_strcpy(char* dst, unsigned int dstSizeInBytes, const char* src)
297 {
298 #if defined(_MSC_VER)
299  return strcpy_s(dst, dstSizeInBytes, src);
300 #else
301  if (dst == 0) {
302  return EINVAL;
303  }
304  if (dstSizeInBytes == 0) {
305  return ERANGE;
306  }
307  if (src == 0) {
308  dst[0] = '\0';
309  return EINVAL;
310  }
311 
312  char* iDst = dst;
313  const char* iSrc = src;
314  size_t remainingSizeInBytes = dstSizeInBytes;
315  while (remainingSizeInBytes > 0 && iSrc[0] != '\0')
316  {
317  iDst[0] = iSrc[0];
318 
319  iDst += 1;
320  iSrc += 1;
321  remainingSizeInBytes -= 1;
322  }
323 
324  if (remainingSizeInBytes > 0) {
325  iDst[0] = '\0';
326  } else {
327  dst[0] = '\0';
328  return ERANGE;
329  }
330 
331  return 0;
332 #endif
333 }
334 
335 
336 DRFSW_PRIVATE int drfsw_event_init(drfsw_event* pEvent, drfsw_event_type type, const char* absolutePath, const char* absolutePathNew, const char* absoluteBasePath, const char* absoluteBasePathNew)
337 {
338  if (pEvent != NULL)
339  {
340  pEvent->type = type;
341 
342  if (absolutePath != NULL) {
343  drfsw_strcpy(pEvent->absolutePath, DRFSW_MAX_PATH, absolutePath);
344  } else {
345  drfsw_zeromemory(pEvent->absolutePath, DRFSW_MAX_PATH);
346  }
347 
348  if (absolutePathNew != NULL) {
349  drfsw_strcpy(pEvent->absolutePathNew, DRFSW_MAX_PATH, absolutePathNew);
350  } else {
351  drfsw_zeromemory(pEvent->absolutePathNew, DRFSW_MAX_PATH);
352  }
353 
354 
355  if (absoluteBasePath != NULL) {
356  drfsw_strcpy(pEvent->absoluteBasePath, DRFSW_MAX_PATH, absoluteBasePath);
357  } else {
358  drfsw_zeromemory(pEvent->absoluteBasePath, DRFSW_MAX_PATH);
359  }
360 
361  if (absoluteBasePathNew != NULL) {
362  drfsw_strcpy(pEvent->absoluteBasePathNew, DRFSW_MAX_PATH, absoluteBasePathNew);
363  } else {
364  drfsw_zeromemory(pEvent->absoluteBasePathNew, DRFSW_MAX_PATH);
365  }
366 
367  return 1;
368  }
369 
370  return 0;
371 }
372 
373 
374 typedef struct
375 {
376  // The buffer containing the events in the queue.
377  drfsw_event* pBuffer;
378 
379  // The size of the buffer, in drfsw_event's.
380  unsigned int bufferSize;
381 
382  // The number of items in the queue.
383  unsigned int count;
384 
385  // The index of the first item in the queue.
386  unsigned int indexFirst;
387 
388 #if defined(_WIN32)
389  // The semaphore for blocking in drfsw_next_event().
390  HANDLE hSemaphore;
391 
392  // The mutex for synchronizing access to the buffer. This is needed because drfsw_next_event() will need to read the buffer while
393  // another thread is filling it with events. In addition, it will help to keep drfsw_next_event() and drfsw_peek_event() playing
394  // nicely with each other.
395  HANDLE hLock;
396 #endif
397 
398 } drfsw_event_queue;
399 
400 DRFSW_PRIVATE int drfsw_event_queue_init(drfsw_event_queue* pQueue)
401 {
402  if (pQueue != NULL)
403  {
404  pQueue->pBuffer = NULL;
405  pQueue->bufferSize = 0;
406  pQueue->indexFirst = 0;
407  pQueue->count = 0;
408 
409 #if defined(_WIN32)
410  pQueue->hSemaphore = CreateSemaphoreW(NULL, 0, DRFSW_EVENT_QUEUE_SIZE, NULL);
411  if (pQueue->hSemaphore == NULL)
412  {
413  drfsw_free(pQueue->pBuffer);
414  return 0;
415  }
416 
417  pQueue->hLock = CreateEventW(NULL, FALSE, TRUE, NULL);
418  if (pQueue->hLock == NULL)
419  {
420  CloseHandle(pQueue->hSemaphore);
421  drfsw_free(pQueue->pBuffer);
422  return 0;
423  }
424 #endif
425 
426  return 1;
427  }
428 
429  return 0;
430 }
431 
432 DRFSW_PRIVATE void drfsw_event_queue_uninit(drfsw_event_queue* pQueue)
433 {
434  if (pQueue != NULL)
435  {
436  drfsw_free(pQueue->pBuffer);
437  pQueue->pBuffer = NULL;
438 
439  pQueue->bufferSize = 0;
440  pQueue->indexFirst = 0;
441  pQueue->count = 0;
442 
443 #if defined(_WIN32)
444  CloseHandle(pQueue->hSemaphore);
445  pQueue->hSemaphore = NULL;
446 
447  CloseHandle(pQueue->hLock);
448  pQueue->hLock = NULL;
449 #endif
450  }
451 }
452 
453 DRFSW_PRIVATE unsigned int drfsw_event_queue_getcount(drfsw_event_queue* pQueue)
454 {
455  if (pQueue != NULL)
456  {
457  return pQueue->count;
458  }
459 
460  return 0;
461 }
462 
463 DRFSW_PRIVATE void drfsw_event_queue_inflate(drfsw_event_queue* pQueue)
464 {
465  if (pQueue != NULL)
466  {
467  unsigned int newBufferSize = pQueue->bufferSize + 1;
468  if (pQueue->bufferSize > 0)
469  {
470  newBufferSize = pQueue->bufferSize*2;
471  }
472 
473  drfsw_event* pOldBuffer = pQueue->pBuffer;
474  drfsw_event* pNewBuffer = (drfsw_event*)drfsw_malloc(newBufferSize * sizeof(drfsw_event));
475 
476  for (unsigned int iDst = 0; iDst < pQueue->count; ++iDst)
477  {
478  unsigned int iSrc = (pQueue->indexFirst + iDst) % pQueue->bufferSize;
479  drfsw_memcpy(pNewBuffer + iDst, pOldBuffer + iSrc, sizeof(drfsw_event));
480  }
481 
482 
483  pQueue->bufferSize = newBufferSize;
484  pQueue->pBuffer = pNewBuffer;
485  pQueue->indexFirst = 0;
486 
487  drfsw_free(pOldBuffer);
488  }
489 }
490 
491 DRFSW_PRIVATE int drfsw_event_queue_pushback(drfsw_event_queue* pQueue, drfsw_event* pEvent)
492 {
493  if (pQueue != NULL)
494  {
495  if (pEvent != NULL)
496  {
497  unsigned int count = drfsw_event_queue_getcount(pQueue);
499  {
500  // We've hit the limit.
501  return 0;
502  }
503 
504  if (count == pQueue->bufferSize)
505  {
506  drfsw_event_queue_inflate(pQueue);
507  assert(count < pQueue->bufferSize);
508  }
509 
510 
511  // Insert the value.
512  unsigned int iDst = (pQueue->indexFirst + pQueue->count) % pQueue->bufferSize;
513  drfsw_memcpy(pQueue->pBuffer + iDst, pEvent, sizeof(drfsw_event));
514 
515 
516  // Increment the counter.
517  pQueue->count += 1;
518 
519 
520  return 1;
521  }
522  }
523 
524  return 0;
525 }
526 
527 DRFSW_PRIVATE int drfsw_event_queue_pop(drfsw_event_queue* pQueue, drfsw_event* pEventOut)
528 {
529  if (pQueue != NULL && pQueue->count > 0)
530  {
531  if (pEventOut != NULL)
532  {
533  drfsw_memcpy(pEventOut, pQueue->pBuffer + pQueue->indexFirst, sizeof(drfsw_event));
534  pQueue->indexFirst = (pQueue->indexFirst + 1) % pQueue->bufferSize;
535  }
536 
537  pQueue->count -= 1;
538 
539 
540  return 1;
541  }
542 
543  return 0;
544 }
545 
546 
547 
548 // A simple function for appending a relative path to an absolute path. This does not resolve "." and ".." components.
549 DRFSW_PRIVATE int drfsw_make_absolute_path(const char* absolutePart, const char* relativePart, char absolutePathOut[DRFSW_MAX_PATH])
550 {
551  size_t absolutePartLength = strlen(absolutePart);
552  size_t relativePartLength = strlen(relativePart);
553 
554  if (absolutePartLength > 0)
555  {
556  if (absolutePart[absolutePartLength - 1] == '/')
557  {
558  absolutePartLength -= 1;
559  }
560 
561  if (absolutePartLength > DRFSW_MAX_PATH)
562  {
563  absolutePartLength = DRFSW_MAX_PATH - 1;
564  }
565  }
566 
567  if (absolutePartLength + relativePartLength + 1 > DRFSW_MAX_PATH)
568  {
569  relativePartLength = DRFSW_MAX_PATH - 1 - absolutePartLength - 1; // -1 for the null terminate and -1 for the slash.
570  }
571 
572 
573  // Absolute part.
574  memcpy(absolutePathOut, absolutePart, absolutePartLength);
575 
576  // Slash.
577  absolutePathOut[absolutePartLength] = '/';
578 
579  // Relative part.
580  memcpy(absolutePathOut + absolutePartLength + 1, relativePart, relativePartLength);
581 
582  // Null terminator.
583  absolutePathOut[absolutePartLength + 1 + relativePartLength] = '\0';
584 
585 
586  return 1;
587 }
588 
589 // Replaces the back slashes with forward slashes in the given string. This operates on the string in place.
590 DRFSW_PRIVATE int drfsw_to_forward_slashes(char* path)
591 {
592  if (path != NULL)
593  {
594  unsigned int counter = 0;
595  while (*path++ != '\0' && counter++ < DRFSW_MAX_PATH)
596  {
597  if (*path == '\\')
598  {
599  *path = '/';
600  }
601  }
602 
603  return 1;
604  }
605 
606  return 0;
607 }
608 
609 
610 typedef struct
611 {
612  // A pointer to the buffer containing pointers to the objects.
613  void** buffer;
614 
615  // The size of the buffer, in pointers.
616  unsigned int bufferSize;
617 
618  // The number of pointers in the list.
619  unsigned int count;
620 
621 } drfsw_list;
622 
623 DRFSW_PRIVATE int drfsw_list_init(drfsw_list* pList)
624 {
625  if (pList != NULL)
626  {
627  pList->buffer = NULL;
628  pList->bufferSize = 0;
629  pList->count = 0;
630 
631  return 1;
632  }
633 
634  return 0;
635 }
636 
637 DRFSW_PRIVATE void drfsw_list_uninit(drfsw_list* pList)
638 {
639  if (pList != NULL)
640  {
641  drfsw_free(pList->buffer);
642  }
643 }
644 
645 DRFSW_PRIVATE void drfsw_list_inflate(drfsw_list* pList)
646 {
647  if (pList != NULL)
648  {
649  unsigned int newBufferSize = pList->bufferSize + 1;
650  if (pList->bufferSize > 0)
651  {
652  newBufferSize = pList->bufferSize*2;
653  }
654 
655  void** pOldBuffer = pList->buffer;
656  void** pNewBuffer = (void**)drfsw_malloc(newBufferSize*sizeof(void*));
657 
658  // Move everything over to the new buffer.
659  for (unsigned int i = 0; i < pList->count; ++i)
660  {
661  pNewBuffer[i] = pOldBuffer[i];
662  }
663 
664 
665  pList->bufferSize = newBufferSize;
666  pList->buffer = pNewBuffer;
667  }
668 }
669 
670 DRFSW_PRIVATE void drfsw_list_pushback(drfsw_list* pList, void* pItem)
671 {
672  if (pList != NULL)
673  {
674  if (pList->count == pList->bufferSize)
675  {
676  drfsw_list_inflate(pList);
677  assert(pList->count < pList->bufferSize);
678 
679  pList->buffer[pList->count] = pItem;
680  pList->count += 1;
681  }
682  }
683 }
684 
685 DRFSW_PRIVATE void drfsw_list_removebyindex(drfsw_list* pList, unsigned int index)
686 {
687  if (pList != NULL)
688  {
689  assert(index < pList->count);
690  assert(pList->count > 0);
691 
692  // Just move everything down one slot.
693  for (unsigned int i = index; index < pList->count - 1; ++i)
694  {
695  pList->buffer[i] = pList->buffer[i + 1];
696  }
697 
698  pList->count -= 1;
699  }
700 }
701 
702 
703 
704 #if defined(_WIN32)
705 #define DRFSW_MAX_PATH_W (DRFSW_MAX_PATH / sizeof(wchar_t))
706 
708 // ReadDirectoryChangesW
709 
710 static const int WIN32_RDC_PENDING_WATCH = (1 << 0);
711 static const int WIN32_RDC_PENDING_DELETE = (1 << 1);
712 
713 
714 // Replaces the forward slashes to back slashes for use with Win32. This operates on the string in place.
715 DRFSW_PRIVATE int drfsw_to_back_slashes_wchar(wchar_t* path)
716 {
717  if (path != NULL)
718  {
719  unsigned int counter = 0;
720  while (*path++ != L'\0' && counter++ < DRFSW_MAX_PATH_W)
721  {
722  if (*path == L'/')
723  {
724  *path = L'\\';
725  }
726  }
727 
728  return 1;
729  }
730 
731  return 0;
732 }
733 
734 // Converts a UTF-8 string to wchar_t for use with Win32. Free the returned pointer with drfsw_free().
735 DRFSW_PRIVATE int drfsw_utf8_to_wchar(const char* str, wchar_t wstrOut[DRFSW_MAX_PATH_W])
736 {
737  int wcharsWritten = MultiByteToWideChar(CP_UTF8, 0, str, -1, wstrOut, DRFSW_MAX_PATH_W);
738  if (wcharsWritten > 0)
739  {
740  assert((unsigned int)wcharsWritten <= DRFSW_MAX_PATH_W);
741  return 1;
742  }
743 
744  return 0;
745 }
746 
747 DRFSW_PRIVATE int drfsw_wchar_to_utf8(const wchar_t* wstr, int wstrCC, char pathOut[DRFSW_MAX_PATH])
748 {
749  int bytesWritten = WideCharToMultiByte(CP_UTF8, 0, wstr, wstrCC, pathOut, DRFSW_MAX_PATH - 1, NULL, NULL);
750  if (bytesWritten > 0)
751  {
752  assert((unsigned int)bytesWritten < DRFSW_MAX_PATH);
753  pathOut[bytesWritten] = '\0';
754 
755  return 1;
756  }
757 
758  return 0;
759 }
760 
761 // Converts a UTF-8 path to wchar_t and converts the slashes to backslashes for use with Win32. Free the returned pointer with drfsw_free().
762 DRFSW_PRIVATE int drfsw_to_win32_path_wchar(const char* path, wchar_t wpathOut[DRFSW_MAX_PATH_W])
763 {
764  if (drfsw_utf8_to_wchar(path, wpathOut))
765  {
766  return drfsw_to_back_slashes_wchar(wpathOut);
767  }
768 
769  return 0;
770 }
771 
772 // Converts a wchar_t Win32 path to a char unix style path (forward slashes instead of back).
773 DRFSW_PRIVATE int drfsw_from_win32_path(const wchar_t* wpath, int wpathCC, char pathOut[DRFSW_MAX_PATH])
774 {
775  if (drfsw_wchar_to_utf8(wpath, wpathCC, pathOut))
776  {
777  return drfsw_to_forward_slashes(pathOut);
778  }
779 
780  return 0;
781 }
782 
783 
784 DRFSW_PRIVATE VOID CALLBACK drfsw_win32_completionroutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
785 DRFSW_PRIVATE VOID CALLBACK drfsw_win32_schedulewatchAPC(ULONG_PTR dwParam);
786 DRFSW_PRIVATE VOID CALLBACK drfsw_win32_cancelioAPC(ULONG_PTR dwParam);
787 
788 typedef struct
789 {
790  // Thelist containing pointers to the watched directory objects. This is not thread safe.
791  drfsw_list list;
792 
793  // The lock for synchronizing access to the list.
794  HANDLE hLock;
795 
796 } drfsw_directory_list_win32;
797 
798 // Structure representing the watcher context for the Win32 RDC method.
799 typedef struct
800 {
801  // The list of watched directories.
802  drfsw_directory_list_win32 watchedDirectories;
803 
804  // The event queue.
805  drfsw_event_queue eventQueue;
806 
807 
808  // A handle to the watcher thread.
809  HANDLE hThread;
810 
811  // The event that will become signaled when the watcher thread needs to be terminated.
812  HANDLE hTerminateEvent;
813 
814  // The semaphore which is used when deleting a watched folder. This starts off at 0, and the maximum count is 1. When a watched
815  // directory is removed, the calling thread will wait on this semaphore while the worker thread does the deletion.
816  HANDLE hDeleteDirSemaphore;
817 
818  // Whether or not the watch thread needs to be terminated.
819  BOOL terminateThread;
820 
821 } drfsw_context_win32;
822 
823 DRFSW_PRIVATE drfsw_context* drfsw_create_context_win32(void);
824 DRFSW_PRIVATE void drfsw_delete_context_win32(drfsw_context_win32* pContext);
825 DRFSW_PRIVATE int drfsw_add_directory_win32(drfsw_context_win32* pContext, const char* absolutePath);
826 DRFSW_PRIVATE void drfsw_remove_directory_win32(drfsw_context_win32* pContext, const char* absolutePath);
827 DRFSW_PRIVATE void drfsw_remove_all_directories_win32(drfsw_context_win32* pContext);
828 DRFSW_PRIVATE int drfsw_is_watching_directory_win32(drfsw_context_win32* pContext, const char* absolutePath);
829 DRFSW_PRIVATE int drfsw_next_event_win32(drfsw_context_win32* pContext, drfsw_event* pEventOut);
830 DRFSW_PRIVATE int drfsw_peek_event_win32(drfsw_context_win32* pContext, drfsw_event* pEventOut);
831 DRFSW_PRIVATE void drfsw_postevent_win32(drfsw_context_win32* pContext, drfsw_event* pEvent);
832 
833 
834 // Structure representing a directory that's being watched with the Win32 RDC method.
835 typedef struct
836 {
837  // A pointer to the context that owns this directory.
838  drfsw_context_win32* pContext;
839 
840  // The absolute path of the directory being watched.
841  char absolutePath[DRFSW_MAX_PATH];
842 
843  // The handle representing the directory. This is created with CreateFile() which means the directory itself will become locked
844  // because the operating system see's it as "in use". It is possible to modify the files and folder inside the directory, though.
845  HANDLE hDirectory;
846 
847  // This is required for for ReadDirectoryChangesW().
848  OVERLAPPED overlapped;
849 
850  // A pointer to the buffer containing the notification objects that is passed to the notification callback specified with
851  // ReadDirectoryChangesW(). This must be aligned to a DWORD boundary, but drfsw_malloc() will do that for us, so that should not
852  // be an issue.
853  FILE_NOTIFY_INFORMATION* pFNIBuffer1;
854  FILE_NOTIFY_INFORMATION* pFNIBuffer2;
855 
856  // The size of the file notification information buffer, in bytes.
857  DWORD fniBufferSizeInBytes;
858 
859  // Flags describing the state of the directory.
860  int flags;
861 
862 } drfsw_directory_win32;
863 
864 DRFSW_PRIVATE int drfsw_directory_win32_beginwatch(drfsw_directory_win32* pDirectory);
865 
866 
867 DRFSW_PRIVATE void drfsw_directory_win32_uninit(drfsw_directory_win32* pDirectory)
868 {
869  if (pDirectory != NULL)
870  {
871  if (pDirectory->hDirectory != NULL)
872  {
873  CloseHandle(pDirectory->hDirectory);
874  pDirectory->hDirectory = NULL;
875  }
876 
877  drfsw_free(pDirectory->pFNIBuffer1);
878  pDirectory->pFNIBuffer1 = NULL;
879 
880  drfsw_free(pDirectory->pFNIBuffer2);
881  pDirectory->pFNIBuffer2 = NULL;
882  }
883 }
884 
885 DRFSW_PRIVATE int drfsw_directory_win32_init(drfsw_directory_win32* pDirectory, drfsw_context_win32* pContext, const char* absolutePath)
886 {
887  if (pDirectory != NULL)
888  {
889  pDirectory->pContext = pContext;
890  drfsw_zeromemory(pDirectory->absolutePath, DRFSW_MAX_PATH);
891  pDirectory->hDirectory = NULL;
892  pDirectory->pFNIBuffer1 = NULL;
893  pDirectory->pFNIBuffer2 = NULL;
894  pDirectory->fniBufferSizeInBytes = 0;
895  pDirectory->flags = 0;
896 
897  size_t length = strlen(absolutePath);
898  if (length > 0)
899  {
900  memcpy(pDirectory->absolutePath, absolutePath, length);
901  pDirectory->absolutePath[length] = '\0';
902 
903  wchar_t absolutePathWithBackSlashes[DRFSW_MAX_PATH_W];
904  if (drfsw_to_win32_path_wchar(absolutePath, absolutePathWithBackSlashes))
905  {
906  pDirectory->hDirectory = CreateFileW(
907  absolutePathWithBackSlashes,
908  FILE_LIST_DIRECTORY,
909  FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
910  NULL,
911  OPEN_EXISTING,
912  FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
913  NULL);
914  if (pDirectory->hDirectory != INVALID_HANDLE_VALUE)
915  {
916  // From MSDN:
917  //
918  // Using a completion routine. To receive notification through a completion routine, do not associate the directory with a
919  // completion port. Specify a completion routine in lpCompletionRoutine. This routine is called whenever the operation has
920  // been completed or canceled while the thread is in an alertable wait state. The hEvent member of the OVERLAPPED structure
921  // is not used by the system, so you can use it yourself.
922  ZeroMemory(&pDirectory->overlapped, sizeof(pDirectory->overlapped));
923  pDirectory->overlapped.hEvent = pDirectory;
924 
925  pDirectory->fniBufferSizeInBytes = WIN32_RDC_FNI_COUNT * sizeof(FILE_NOTIFY_INFORMATION);
926  pDirectory->pFNIBuffer1 = (FILE_NOTIFY_INFORMATION*)drfsw_malloc(pDirectory->fniBufferSizeInBytes);
927  pDirectory->pFNIBuffer2 = (FILE_NOTIFY_INFORMATION*)drfsw_malloc(pDirectory->fniBufferSizeInBytes);
928  if (pDirectory->pFNIBuffer1 != NULL && pDirectory->pFNIBuffer2 != NULL)
929  {
930  // At this point the directory is initialized, however it is not yet being watched. The watch needs to be triggered from
931  // the worker thread. To do this, we need to signal hPendingWatchEvent, however that needs to be done after the context
932  // has added the directory to it's internal list.
933  return 1;
934  }
935  else
936  {
937  drfsw_directory_win32_uninit(pDirectory);
938  }
939  }
940  else
941  {
942  drfsw_directory_win32_uninit(pDirectory);
943  }
944  }
945  else
946  {
947  drfsw_directory_win32_uninit(pDirectory);
948  }
949  }
950  }
951 
952  return 0;
953 }
954 
955 DRFSW_PRIVATE int drfsw_directory_win32_schedulewatch(drfsw_directory_win32* pDirectory)
956 {
957  if (pDirectory != NULL)
958  {
959  pDirectory->flags |= WIN32_RDC_PENDING_WATCH;
960  QueueUserAPC(drfsw_win32_schedulewatchAPC, pDirectory->pContext->hThread, (ULONG_PTR)pDirectory);
961 
962  return 1;
963  }
964 
965  return 0;
966 }
967 
968 DRFSW_PRIVATE int drfsw_directory_win32_scheduledelete(drfsw_directory_win32* pDirectory)
969 {
970  if (pDirectory != NULL)
971  {
972  pDirectory->flags |= WIN32_RDC_PENDING_DELETE;
973  QueueUserAPC(drfsw_win32_cancelioAPC, pDirectory->pContext->hThread, (ULONG_PTR)pDirectory);
974 
975  return 1;
976  }
977 
978  return 0;
979 }
980 
981 DRFSW_PRIVATE int drfsw_directory_win32_beginwatch(drfsw_directory_win32* pDirectory)
982 {
983  // This function should only be called from the worker thread.
984 
985  if (pDirectory != NULL)
986  {
987  DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION;
988  DWORD dwBytes = 0;
989  if (ReadDirectoryChangesW(pDirectory->hDirectory, pDirectory->pFNIBuffer1, pDirectory->fniBufferSizeInBytes, TRUE, dwNotifyFilter, &dwBytes, &pDirectory->overlapped, drfsw_win32_completionroutine))
990  {
991  pDirectory->flags &= ~WIN32_RDC_PENDING_WATCH;
992  return 1;
993  }
994  }
995 
996  return 0;
997 }
998 
999 
1000 
1001 
1002 DRFSW_PRIVATE int drfsw_directory_list_win32_init(drfsw_directory_list_win32* pDirectories)
1003 {
1004  if (pDirectories != NULL)
1005  {
1006  drfsw_list_init(&pDirectories->list);
1007 
1008  pDirectories->hLock = CreateEvent(NULL, FALSE, TRUE, NULL);
1009  if (pDirectories->hLock != NULL)
1010  {
1011  return 1;
1012  }
1013  }
1014 
1015  return 0;
1016 }
1017 
1018 DRFSW_PRIVATE void drfsw_directory_list_win32_uninit(drfsw_directory_list_win32* pDirectories)
1019 {
1020  if (pDirectories != NULL)
1021  {
1022  drfsw_list_uninit(&pDirectories->list);
1023 
1024  CloseHandle(pDirectories->hLock);
1025  pDirectories->hLock = NULL;
1026  }
1027 }
1028 
1029 
1030 
1031 
1032 
1033 DRFSW_PRIVATE VOID CALLBACK drfsw_win32_completionroutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
1034 {
1035  drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)lpOverlapped->hEvent;
1036  if (pDirectory != NULL)
1037  {
1038  if (dwErrorCode == ERROR_OPERATION_ABORTED)
1039  {
1040  // When we get here, CancelIo() was called on the directory. We treat this as a signal that the context has requested that the directory
1041  // be deleted. At this point the directory has been removed from the context's internal list and we just need to uninitialize and free
1042  // the directory object.
1043  drfsw_context_win32* pContext = pDirectory->pContext;
1044  drfsw_directory_win32_uninit(pDirectory);
1045  drfsw_free(pDirectory);
1046 
1047  ReleaseSemaphore(pContext->hDeleteDirSemaphore, 1, NULL);
1048  return;
1049  }
1050 
1051  assert(dwNumberOfBytesTransfered >= sizeof(FILE_NOTIFY_INFORMATION));
1052 
1053  // At this point we're not actually watching the directory - there is a chance that as we're executing this section there
1054  // are changes to the file system whose events will go undetected. We need to call ReadDirectoryChangesW() again as soon as
1055  // possible. This routine is always called from the worker thread, and only while it's in an alertable state. Therefore it
1056  // is safe for us to use a simple front/back buffer type system to make it as quick as possible to resume watching operations.
1057  FILE_NOTIFY_INFORMATION* temp = pDirectory->pFNIBuffer1;
1058  pDirectory->pFNIBuffer1 = pDirectory->pFNIBuffer2;
1059  pDirectory->pFNIBuffer2 = temp;
1060 
1061 
1062  // Begin watching again (call ReadDirectoryChangesW() again) as soon as possible. At this point we are not currently watching
1063  // for changes to the directory, so we need to start that before posting events. To start watching we need to send a signal to
1064  // the worker thread which will do the actual call to ReadDirectoryChangesW().
1065  drfsw_directory_win32_schedulewatch(pDirectory);
1066 
1067 
1068  // Now we loop through all of our notifications and post the event to the context for later processing by drfsw_next_event()
1069  // and drfsw_peek_event().
1070  char absolutePathOld[DRFSW_MAX_PATH];
1071  char absoluteBasePathOld[DRFSW_MAX_PATH];
1072  drfsw_context_win32* pContext = pDirectory->pContext; // Just for convenience.
1073 
1074 
1075  FILE_NOTIFY_INFORMATION* pFNI = pDirectory->pFNIBuffer2;
1076  for (;;)
1077  {
1078  char relativePath[DRFSW_MAX_PATH];
1079  if (drfsw_from_win32_path(pFNI->FileName, pFNI->FileNameLength / sizeof(wchar_t), relativePath))
1080  {
1081  char absolutePath[DRFSW_MAX_PATH];
1082  if (drfsw_make_absolute_path(pDirectory->absolutePath, relativePath, absolutePath))
1083  {
1084  switch (pFNI->Action)
1085  {
1086  case FILE_ACTION_ADDED:
1087  {
1088  drfsw_event e;
1089  if (drfsw_event_init(&e, drfsw_event_type_created, absolutePath, NULL, pDirectory->absolutePath, NULL))
1090  {
1091  drfsw_postevent_win32(pContext, &e);
1092  }
1093 
1094  break;
1095  }
1096 
1097  case FILE_ACTION_REMOVED:
1098  {
1099  drfsw_event e;
1100  if (drfsw_event_init(&e, drfsw_event_type_deleted, absolutePath, NULL, pDirectory->absolutePath, NULL))
1101  {
1102  drfsw_postevent_win32(pContext, &e);
1103  }
1104 
1105  break;
1106  }
1107 
1108  case FILE_ACTION_RENAMED_OLD_NAME:
1109  {
1110  drfsw_strcpy(absolutePathOld, sizeof(absolutePathOld), absolutePath);
1111  drfsw_strcpy(absoluteBasePathOld, sizeof(absoluteBasePathOld), pDirectory->absolutePath);
1112 
1113  break;
1114  }
1115  case FILE_ACTION_RENAMED_NEW_NAME:
1116  {
1117  drfsw_event e;
1118  if (drfsw_event_init(&e, drfsw_event_type_renamed, absolutePathOld, absolutePath, absoluteBasePathOld, pDirectory->absolutePath))
1119  {
1120  drfsw_postevent_win32(pContext, &e);
1121  }
1122 
1123  break;
1124  }
1125 
1126  case FILE_ACTION_MODIFIED:
1127  {
1128  drfsw_event e;
1129  if (drfsw_event_init(&e, drfsw_event_type_updated, absolutePath, NULL, pDirectory->absolutePath, NULL))
1130  {
1131  drfsw_postevent_win32(pContext, &e);
1132  }
1133 
1134  break;
1135  }
1136 
1137  default: break;
1138  }
1139  }
1140  }
1141 
1142 
1143 
1144  if (pFNI->NextEntryOffset == 0)
1145  {
1146  break;
1147  }
1148 
1149  pFNI = (FILE_NOTIFY_INFORMATION*)(((char*)pFNI) + pFNI->NextEntryOffset);
1150  }
1151  }
1152 }
1153 
1154 DRFSW_PRIVATE VOID CALLBACK drfsw_win32_schedulewatchAPC(ULONG_PTR dwParam)
1155 {
1156  drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)dwParam;
1157  if (pDirectory != NULL)
1158  {
1159  if ((pDirectory->flags & WIN32_RDC_PENDING_WATCH) != 0)
1160  {
1161  drfsw_directory_win32_beginwatch(pDirectory);
1162  }
1163  }
1164 }
1165 
1166 DRFSW_PRIVATE VOID CALLBACK drfsw_win32_cancelioAPC(ULONG_PTR dwParam)
1167 {
1168  drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)dwParam;
1169  if (pDirectory != NULL)
1170  {
1171  if ((pDirectory->flags & WIN32_RDC_PENDING_DELETE) != 0)
1172  {
1173  // We don't free the directory structure from here. Instead we just call CancelIo(). This will trigger
1174  // the ERROR_OPERATION_ABORTED error in the notification callback which is where the real delete will
1175  // occur. That is also where the synchronization lock is released that the thread that called
1176  // drfsw_delete_directory() is waiting on.
1177  CancelIo(pDirectory->hDirectory);
1178 
1179  // The directory needs to be removed from the context's list. The directory object will be freed in the
1180  // notification callback in response to ERROR_OPERATION_ABORTED which will be triggered by the previous
1181  // call to CancelIo().
1182  for (unsigned int i = 0; i < pDirectory->pContext->watchedDirectories.list.count; ++i)
1183  {
1184  if (pDirectory == pDirectory->pContext->watchedDirectories.list.buffer[i])
1185  {
1186  drfsw_list_removebyindex(&pDirectory->pContext->watchedDirectories.list, i);
1187  break;
1188  }
1189  }
1190  }
1191  }
1192 }
1193 
1194 
1195 
1196 
1197 DRFSW_PRIVATE DWORD WINAPI _WatcherThreadProc_RDC(drfsw_context_win32 *pContextRDC)
1198 {
1199  while (!pContextRDC->terminateThread)
1200  {
1201  // Important that we use the Ex version here because we need to put the thread into an alertable state (last argument). If the thread is not put into
1202  // an alertable state, ReadDirectoryChangesW() won't ever call the notification event.
1203  DWORD rc = WaitForSingleObjectEx(pContextRDC->hTerminateEvent, INFINITE, TRUE);
1204  switch (rc)
1205  {
1206  case WAIT_OBJECT_0 + 0:
1207  {
1208  // The context has signaled that it needs to be deleted.
1209  pContextRDC->terminateThread = TRUE;
1210  break;
1211  }
1212 
1213  case WAIT_IO_COMPLETION:
1214  default:
1215  {
1216  // Nothing to do.
1217  break;
1218  }
1219  }
1220  }
1221 
1222  return 0;
1223 }
1224 
1225 DRFSW_PRIVATE drfsw_context* drfsw_create_context_win32()
1226 {
1227  drfsw_context_win32* pContext = (drfsw_context_win32*)drfsw_malloc(sizeof(drfsw_context_win32));
1228  if (pContext != NULL)
1229  {
1230  if (drfsw_directory_list_win32_init(&pContext->watchedDirectories))
1231  {
1232  if (drfsw_event_queue_init(&pContext->eventQueue))
1233  {
1234  pContext->hTerminateEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
1235  pContext->hDeleteDirSemaphore = CreateSemaphoreW(NULL, 0, 1, NULL);
1236  pContext->terminateThread = FALSE;
1237 
1238  if (pContext->hTerminateEvent != NULL)
1239  {
1240  pContext->hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_WatcherThreadProc_RDC, pContext, 0, NULL);
1241  if (pContext->hThread != NULL)
1242  {
1243  // Everything went well.
1244  }
1245  else
1246  {
1247  CloseHandle(pContext->hTerminateEvent);
1248 
1249  drfsw_free(pContext);
1250  pContext = NULL;
1251  }
1252  }
1253  else
1254  {
1255  drfsw_free(pContext);
1256  pContext = NULL;
1257  }
1258  }
1259  }
1260  }
1261 
1262  return (drfsw_context*)pContext;
1263 }
1264 
1265 DRFSW_PRIVATE void drfsw_delete_context_win32(drfsw_context_win32* pContext)
1266 {
1267  if (pContext != NULL)
1268  {
1269  // Every watched directory needs to be removed.
1270  drfsw_remove_all_directories_win32(pContext);
1271 
1272 
1273  // Signal the close event, and wait for the thread to finish.
1274  SignalObjectAndWait(pContext->hTerminateEvent, pContext->hThread, INFINITE, FALSE);
1275 
1276  // The thread has finished, so close the handle.
1277  CloseHandle(pContext->hThread);
1278  pContext->hThread = NULL;
1279 
1280 
1281  // We need to wait for the event queue to finish up before deleting the context for real. If we don't do this nextevent() may try
1282  // to access the context and then crash.
1283  WaitForSingleObject(pContext->eventQueue.hLock, INFINITE);
1284  drfsw_event_queue_uninit(&pContext->eventQueue); // <-- This will close pContext->eventQueue.hLock so no need to call SetEvent().
1285 
1286 
1287  // The worker thread events need to be closed.
1288  CloseHandle(pContext->hTerminateEvent);
1289  pContext->hTerminateEvent = NULL;
1290 
1291 
1292  // The semaphore we use for deleting directories.
1293  CloseHandle(pContext->hDeleteDirSemaphore);
1294  pContext->hDeleteDirSemaphore = NULL;
1295 
1296 
1297  drfsw_directory_list_win32_uninit(&pContext->watchedDirectories);
1298 
1299  // Free the memory.
1300  drfsw_free(pContext);
1301  }
1302 }
1303 
1304 DRFSW_PRIVATE drfsw_directory_win32* drfsw_find_directory_win32(drfsw_context_win32* pContext, const char* absolutePath, unsigned int* pIndexOut)
1305 {
1306  assert(pContext != NULL);
1307 
1308  for (unsigned int iDirectory = 0; iDirectory < pContext->watchedDirectories.list.count; ++iDirectory)
1309  {
1310  drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)pContext->watchedDirectories.list.buffer[iDirectory];
1311  if (pDirectory != NULL)
1312  {
1313  if (strcmp(absolutePath, pDirectory->absolutePath) == 0)
1314  {
1315  if (pIndexOut != NULL)
1316  {
1317  *pIndexOut = iDirectory;
1318  }
1319 
1320  return pDirectory;
1321  }
1322  }
1323  }
1324 
1325  return NULL;
1326 }
1327 
1328 DRFSW_PRIVATE int drfsw_add_directory_win32(drfsw_context_win32* pContext, const char* absolutePath)
1329 {
1330  if (pContext != NULL)
1331  {
1332  drfsw_directory_win32* pDirectory = (drfsw_directory_win32*)drfsw_malloc(sizeof(drfsw_directory_win32));
1333  if (pDirectory != NULL)
1334  {
1335  if (!drfsw_is_watching_directory_win32(pContext, absolutePath))
1336  {
1337  if (drfsw_directory_win32_init(pDirectory, pContext, absolutePath))
1338  {
1339  // At this point the directory has been initialized, but it's not yet being watched. To do this, we need to call ReadDirectoryChangesW() from
1340  // the worker thread which means we need to signal an event which the worker thread will be waiting on. When the worker thread receives the
1341  // signal, it will iterate over each directory in the context and check for the ones that are pending watching. Therefore, before signaling
1342  // the event, we need to make sure the directory is added to the context's list.
1343  WaitForSingleObject(pContext->watchedDirectories.hLock, INFINITE);
1344  {
1345  drfsw_list_pushback(&pContext->watchedDirectories.list, pDirectory);
1346  }
1347  SetEvent(pContext->watchedDirectories.hLock);
1348 
1349  // The directory is now in the list and we can send the signal.
1350  drfsw_directory_win32_schedulewatch(pDirectory);
1351 
1352 
1353  return 1;
1354  }
1355  else
1356  {
1357  drfsw_free(pDirectory);
1358  pDirectory = NULL;
1359  }
1360  }
1361  else
1362  {
1363  drfsw_free(pDirectory);
1364  pDirectory = NULL;
1365  }
1366  }
1367  }
1368 
1369 
1370 
1371  // An error occured if we got here.
1372  return 0;
1373 }
1374 
1375 DRFSW_PRIVATE void drfsw_remove_directory_win32_no_lock(drfsw_context_win32* pContext, const char* absolutePath)
1376 {
1377  assert(pContext != NULL);
1378 
1379  unsigned int index;
1380  drfsw_directory_win32* pDirectory = drfsw_find_directory_win32(pContext, absolutePath, &index);
1381  if (pDirectory != NULL)
1382  {
1383  // When removing a directory, we need to call CancelIo() on the file handle we created for the directory. The problem is that
1384  // this needs to be called on the worker thread in order for watcher notification callback function to get the correct error
1385  // code. To do this we need to signal an event which the worker thread is waiting on. The worker thread will then call CancelIo()
1386  // which in turn will trigger the correct error code in the notification callback. The notification callback is where the
1387  // the object will be deleted for real and will release the synchronization lock that this function is waiting on below.
1388  drfsw_directory_win32_scheduledelete(pDirectory);
1389 
1390  // Now we need to wait for the relevant event to become signaled. This will become signaled when the worker thread has finished
1391  // deleting the file handle and whatnot from it's end.
1392  WaitForSingleObject(pContext->hDeleteDirSemaphore, INFINITE);
1393  }
1394 }
1395 
1396 DRFSW_PRIVATE void drfsw_remove_directory_win32(drfsw_context_win32* pContext, const char* absolutePath)
1397 {
1398  if (pContext != NULL)
1399  {
1400  WaitForSingleObject(pContext->watchedDirectories.hLock, INFINITE);
1401  {
1402  drfsw_remove_directory_win32_no_lock(pContext, absolutePath);
1403  }
1404  SetEvent(pContext->watchedDirectories.hLock);
1405  }
1406 }
1407 
1408 DRFSW_PRIVATE void drfsw_remove_all_directories_win32(drfsw_context_win32* pContext)
1409 {
1410  if (pContext != NULL)
1411  {
1412  WaitForSingleObject(pContext->watchedDirectories.hLock, INFINITE);
1413  {
1414  for (unsigned int i = pContext->watchedDirectories.list.count; i > 0; --i)
1415  {
1416  drfsw_remove_directory_win32_no_lock(pContext, ((drfsw_directory_win32*)pContext->watchedDirectories.list.buffer[i - 1])->absolutePath);
1417  }
1418  }
1419  SetEvent(pContext->watchedDirectories.hLock);
1420  }
1421 }
1422 
1423 DRFSW_PRIVATE int drfsw_is_watching_directory_win32(drfsw_context_win32* pContext, const char* absolutePath)
1424 {
1425  if (pContext != NULL)
1426  {
1427  return drfsw_find_directory_win32(pContext, absolutePath, NULL) != NULL;
1428  }
1429 
1430  return 0;
1431 }
1432 
1433 
1434 DRFSW_PRIVATE int drfsw_next_event_win32(drfsw_context_win32* pContext, drfsw_event* pEvent)
1435 {
1436  int result = 0;
1437  if (pContext != NULL && !pContext->terminateThread)
1438  {
1439  // Wait for either the semaphore or the thread to terminate.
1440  HANDLE hEvents[2];
1441  hEvents[0] = pContext->hThread;
1442  hEvents[1] = pContext->eventQueue.hSemaphore;
1443 
1444  DWORD rc = WaitForMultipleObjects(sizeof(hEvents) / sizeof(hEvents[0]), hEvents, FALSE, INFINITE);
1445  switch (rc)
1446  {
1447  case WAIT_OBJECT_0 + 0:
1448  {
1449  // The thread has been terminated.
1450  result = 0;
1451  break;
1452  }
1453 
1454  case WAIT_OBJECT_0 + 1:
1455  {
1456  // We're past the semaphore block, so now we can copy of the event. We need to lock the queue before doing this in case another thread wants
1457  // to try pushing another event onto the queue.
1458  if (!pContext->terminateThread)
1459  {
1460  DWORD eventLockResult = WaitForSingleObject(pContext->eventQueue.hLock, INFINITE);
1461  if (eventLockResult == WAIT_OBJECT_0)
1462  {
1463  drfsw_event_queue_pop(&pContext->eventQueue, pEvent);
1464  }
1465  SetEvent(pContext->eventQueue.hLock);
1466 
1467  if (eventLockResult == WAIT_OBJECT_0)
1468  {
1469  result = 1;
1470  }
1471  else
1472  {
1473  // The lock returned early for some reason which means there must have been some kind of error or the context has been destroyed.
1474  result = 0;
1475  }
1476  }
1477 
1478  break;
1479  }
1480 
1481  default: break;
1482  }
1483  }
1484 
1485  return result;
1486 }
1487 
1488 DRFSW_PRIVATE int drfsw_peek_event_win32(drfsw_context_win32* pContext, drfsw_event* pEvent)
1489 {
1490  int result = 0;
1491  if (pContext != NULL)
1492  {
1493  DWORD eventLockResult = WaitForSingleObject(pContext->eventQueue.hLock, INFINITE);
1494  {
1495  // Make sure we decrement our semaphore counter. Don't block here.
1496  WaitForSingleObject(pContext->eventQueue.hSemaphore, 0);
1497 
1498  // Now we just copy over the data by popping it from the queue.
1499  if (eventLockResult == WAIT_OBJECT_0)
1500  {
1501  if (drfsw_event_queue_getcount(&pContext->eventQueue) > 0)
1502  {
1503  drfsw_event_queue_pop(&pContext->eventQueue, pEvent);
1504  result = 1;
1505  }
1506  else
1507  {
1508  result = 0;
1509  }
1510  }
1511  else
1512  {
1513  // Waiting on the event queue lock failed for some reason. It could mean that the context has been deleted.
1514  result = 0;
1515  }
1516  }
1517  SetEvent(pContext->eventQueue.hLock);
1518  }
1519 
1520  return result;
1521 }
1522 
1523 DRFSW_PRIVATE void drfsw_postevent_win32(drfsw_context_win32* pContext, drfsw_event* pEvent)
1524 {
1525  if (pContext != NULL && pEvent != NULL)
1526  {
1527  // Add the event to the queue.
1528  WaitForSingleObject(pContext->eventQueue.hLock, INFINITE);
1529  {
1530  drfsw_event_queue_pushback(&pContext->eventQueue, pEvent);
1531  }
1532  SetEvent(pContext->eventQueue.hLock);
1533 
1534 
1535  // Release the semaphore so that drfsw_next_event() can handle it.
1536  ReleaseSemaphore(pContext->eventQueue.hSemaphore, 1, NULL);
1537  }
1538 }
1539 
1540 
1541 
1543 // Public API for Win32
1544 
1546 {
1547  return drfsw_create_context_win32();
1548 }
1549 
1550 void drfsw_delete_context(drfsw_context* pContext)
1551 {
1552  drfsw_delete_context_win32((drfsw_context_win32*)pContext);
1553 }
1554 
1555 
1556 int drfsw_add_directory(drfsw_context* pContext, const char* absolutePath)
1557 {
1558  return drfsw_add_directory_win32((drfsw_context_win32*)pContext, absolutePath);
1559 }
1560 
1561 void drfsw_remove_directory(drfsw_context* pContext, const char* absolutePath)
1562 {
1563  drfsw_remove_directory_win32((drfsw_context_win32*)pContext, absolutePath);
1564 }
1565 
1567 {
1568  drfsw_remove_all_directories_win32((drfsw_context_win32*)pContext);
1569 }
1570 
1571 int drfsw_is_watching_directory(drfsw_context* pContext, const char* absolutePath)
1572 {
1573  return drfsw_is_watching_directory_win32((drfsw_context_win32*)pContext, absolutePath);
1574 }
1575 
1576 
1577 int drfsw_next_event(drfsw_context* pContext, drfsw_event* pEventOut)
1578 {
1579  return drfsw_next_event_win32((drfsw_context_win32*)pContext, pEventOut);
1580 }
1581 
1582 int drfsw_peek_event(drfsw_context* pContext, drfsw_event* pEventOut)
1583 {
1584  return drfsw_peek_event_win32((drfsw_context_win32*)pContext, pEventOut);
1585 }
1586 
1587 
1588 #endif // Win32
1589 
1590 
1591 #if defined(__clang__)
1592  #pragma GCC diagnostic pop
1593 #endif
1594 
1595 #endif //DR_FSW_IMPLEMENTATION
1596 
1597 
1598 
1599 
1600 /*
1601 This is free and unencumbered software released into the public domain.
1602 
1603 Anyone is free to copy, modify, publish, use, compile, sell, or
1604 distribute this software, either in source code form or as a compiled
1605 binary, for any purpose, commercial or non-commercial, and by any
1606 means.
1607 
1608 In jurisdictions that recognize copyright laws, the author or authors
1609 of this software dedicate any and all copyright interest in the
1610 software to the public domain. We make this dedication for the benefit
1611 of the public at large and to the detriment of our heirs and
1612 successors. We intend this dedication to be an overt act of
1613 relinquishment in perpetuity of all present and future rights to this
1614 software under copyright law.
1615 
1616 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1617 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1618 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1619 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1620 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1621 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1622 OTHER DEALINGS IN THE SOFTWARE.
1623 
1624 For more information, please refer to <http://unlicense.org/>
1625 */
DRFSW_MAX_PATH
#define DRFSW_MAX_PATH
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:105
DRFSW_EVENT_QUEUE_SIZE
#define DRFSW_EVENT_QUEUE_SIZE
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:110
drfsw_event_type
drfsw_event_type
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:114
NULL
#define NULL
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/extras/speex_resampler/thirdparty/resample.c:92
drfsw_event_type
drfsw_event_type
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:114
FALSE
#define FALSE
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/extras/stb_vorbis.c:642
drfsw_delete_context
void drfsw_delete_context(drfsw_context *pContext)
drfsw_next_event
int drfsw_next_event(drfsw_context *pContext, drfsw_event *pEventOut)
drfsw_event::absolutePathNew
char absolutePathNew[DRFSW_MAX_PATH]
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:134
drfsw_event_type_created
@ drfsw_event_type_created
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:116
drfsw_event::type
drfsw_event_type type
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:128
drfsw_event_type_deleted
@ drfsw_event_type_deleted
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:117
drfsw_event_type_updated
@ drfsw_event_type_updated
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:119
drfsw_add_directory
int drfsw_add_directory(drfsw_context *pContext, const char *absolutePath)
drfsw_event::absoluteBasePath
char absoluteBasePath[DRFSW_MAX_PATH]
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:137
TRUE
#define TRUE
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/extras/stb_vorbis.c:641
strcpy_s
DR_INLINE int strcpy_s(char *dst, size_t dstSizeInBytes, const char *src)
Definition: porcupine/demo/c/dr_libs/old/dr.h:157
count
size_t count
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/tests/test_common/ma_test_common.c:31
drfsw_event
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:125
L
#define L
Definition: porcupine/demo/c/dr_libs/tests/external/miniaudio/extras/stb_vorbis.c:5102
drfsw_is_watching_directory
int drfsw_is_watching_directory(drfsw_context *pContext, const char *absolutePath)
drfsw_peek_event
int drfsw_peek_event(drfsw_context *pContext, drfsw_event *pEventOut)
drfsw_remove_all_directories
void drfsw_remove_all_directories(drfsw_context *pContext)
python.pvrecorder.CALLBACK
CALLBACK
Definition: porcupine/demo/c/pvrecorder/sdk/python/pvrecorder.py:17
drfsw_create_context
drfsw_context * drfsw_create_context(void)
drfsw_context
void * drfsw_context
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:145
drfsw_remove_directory
void drfsw_remove_directory(drfsw_context *pContext, const char *absolutePath)
drfsw_event::absoluteBasePathNew
char absoluteBasePathNew[DRFSW_MAX_PATH]
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:140
drfsw_event::absolutePath
char absolutePath[DRFSW_MAX_PATH]
Definition: porcupine/demo/c/dr_libs/old/dr_fsw.h:131
assert.h
drfsw_context
void * drfsw_context
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:145
drfsw_event_type_renamed
@ drfsw_event_type_renamed
Definition: rhino/demo/c/dr_libs/old/dr_fsw.h:118


picovoice_driver
Author(s):
autogenerated on Fri Apr 1 2022 02:13:53