This repository has been archived by the owner on Jan 18, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
dr_fswatcher.h
1625 lines (1310 loc) · 52.8 KB
/
dr_fswatcher.h
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Public Domain. See "unlicense" statement at the end of this file.
// ABOUT
//
// dr_fsw is a simple library for watching for changes to the file system. This is not a full-featured library
// and is only intended for basic use cases.
//
// Limitations:
// - Only Windows is supported at the moment.
//
//
//
// USAGE
//
// This is a single-file library. To use it, do something like the following in one .c file.
// #define DR_FSW_IMPLEMENTATION
// #include "dr_fsw.h"
//
// You can then #include this file in other parts of the program as you would with any other header file.
//
// Example:
// // Startup.
// drfsw_context* context = drfsw_create_context();
// if (context == NULL)
// {
// // There was an error creating the context...
// }
//
// drfsw_add_directory(context, "C:/My/Folder");
// drfsw_add_directory(context, "C:/My/Other/Folder");
//
// ...
//
// // Meanwhile, in another thread...
// void MyOtherThreadEntryProc()
// {
// drfsw_event e;
// while (isMyApplicationStillAlive && drfsw_next_event(context, &e))
// {
// switch (e.type)
// {
// case drfsw_event_type_created: OnFileCreated(e.absolutePath); break;
// case drfsw_event_type_deleted: OnFileDeleted(e.absolutePath); break;
// case drfsw_event_type_renamed: OnFileRenamed(e.absolutePath, e.absolutePathNew); break;
// case drfsw_event_type_updated: OnFileUpdated(e.absolutePath); break;
// default: break;
// }
// }
// }
//
// ...
//
// // Shutdown
// drfsw_context* contextOld = context;
// context = NULL;
// drfsw_delete_context(contextOld);
//
// Directories are watched recursively, so try to avoid using this on high level directories
// like "C:\". Also avoid watching a directory that is a descendant of another directory that's
// already being watched.
//
// It does not matter whether or not paths are specified with forward or back slashes; the
// library will normalize all of that internally depending on the platform. Note, however,
// that events always report their paths with forward slashes.
//
// You don't need to wait for events on a separate thread, however drfsw_next_event() is
// a blocking call, so it's usually best to do so. Alternatively, you can use
// drfsw_peek_event() which is the same, except non-blocking. Avoid using both
// drfsw_next_event() and drfsw_peek_event() at the same time because both will remove
// the event from the internal queue so it likely won't work the way you expect.
//
// The shutdown sequence is a bit strange, but since another thread is accessing that pointer,
// you should set any shared pointers to null before calling drfsw_delete_context(). That way,
// the next call to drfsw_next_event() will pass in a null pointer which will cause it to
// immediately return with zero and thus break the loop and terminate the thread. It is up to
// the application to ensure the pointer passed to drfsw_next_event() is valid. Deleting a context
// will cause a waiting call to drfsw_next_event() to return 0, however there is a chance that the
// context is deleted before drfsw_next_event() has entered into it's wait state. It's up to the
// application to make sure the context remains valid.
//
//
//
// QUICK NOTES
// - Files that are not on the machine's local file system will not be detected (such as files on a network drive).
// - In some cases, renaming files won't be detected. Instead it may be implemented as a delete/create pair.
//
// - Win32: Every directory that is watched becomes "in use" by the operating system. It is still possible
// to modify the files and folders inside the watched directory, however.
// - Win32: There is a known issue with the ReadDirectoryChangesW() watch technique (which is used internally)
// where some events won't get processed if a large number of files change in a short period of time.
#ifndef dr_fsw_h
#define dr_fsw_h
#ifdef __cplusplus
extern "C" {
#endif
// 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
// 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
// not a problem to increase the size if you are encountering issues. Note that increasing this value will increase memory usage
// on both the heap and the stack.
#ifndef DRFSW_MAX_PATH
//#define DRFSW_MAX_PATH 256U
#define DRFSW_MAX_PATH 1024U
//#define DRFSW_MAX_PATH 4096U
#endif
// The maximum size of the event queue before it overflows.
#define DRFSW_EVENT_QUEUE_SIZE 1024U
// The different event types.
typedef enum
{
drfsw_event_type_created,
drfsw_event_type_deleted,
drfsw_event_type_renamed,
drfsw_event_type_updated
} drfsw_event_type;
// Structure containing information about an event.
typedef struct
{
// The type of the event: created, deleted, renamed or updated.
drfsw_event_type type;
// The absolute path of the file. For renamed events, this is the old name.
char absolutePath[DRFSW_MAX_PATH];
// The new file name. This is only used for renamed events. For other event types, this will be an empty string.
char absolutePathNew[DRFSW_MAX_PATH];
// The absolute base path. For renamed events, this is the old base path.
char absoluteBasePath[DRFSW_MAX_PATH];
// 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.
char absoluteBasePathNew[DRFSW_MAX_PATH];
} drfsw_event;
typedef void* drfsw_context;
// Creates a file system watcher.
//
// This will create a background thread that will do the actual checking.
drfsw_context* drfsw_create_context(void);
// Deletes the given file system watcher.
//
// This will not return until the thread watching for changes has returned.
//
// You do not need to remove the watched directories beforehand - this function will make sure everything is cleaned up properly.
void drfsw_delete_context(drfsw_context* pContext);
// Adds a directory to watch. This will watch for files and folders recursively.
int drfsw_add_directory(drfsw_context* pContext, const char* absolutePath);
// Removes a watched directory.
void drfsw_remove_directory(drfsw_context* pContext, const char* absolutePath);
// Helper for removing every watched directory.
void drfsw_remove_all_directories(drfsw_context* pContext);
// Determines whether or not the given directory is being watched.
int drfsw_is_watching_directory(drfsw_context* pContext, const char* absolutePath);
// Waits for an event from the file system.
//
// 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
// will be returned and the memory pointed to by pEventOut will be undefined.
//
// This can be called from any thread, however it should not be called from multiple threads simultaneously.
//
// Use caution when using this combined with drfsw_peek_event(). In almost all cases you should use just one or the other at any
// given time.
//
// It is up to the application to ensure the context is still valid before calling this function.
//
// Example Usage:
//
// void MyFSWatcher() {
// drfsw_event e;
// while (isMyContextStillAlive && drfsw_next_event(context, e)) {
// // Do something with the event...
// }
// }
int drfsw_next_event(drfsw_context* pContext, drfsw_event* pEventOut);
// 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
// removes the event from the queue.
//
// This can be called from any thread, however it should not be called from multiple threads simultaneously.
//
// It is up to the application to ensure the context is still valid before calling this function.
int drfsw_peek_event(drfsw_context* pContext, drfsw_event* pEventOut);
#ifdef __cplusplus
}
#endif
#endif //dr_fsw_h
///////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION
//
///////////////////////////////////////////////////////////////////////////////
#ifdef DR_FSW_IMPLEMENTATION
// NOTES:
//
// Win32 and ReadDirectoryChangesW
//
// Here is how watching for changes via the ReadDirectoryChangesW() works:
// 1) You create a handle to the directory with CreateFile()
// 2) You pass this handle to ReadDirectoryChangesW(), including a pointer to a function that is called when changes to the directory are made.
// 3) From the aforementioned callback, ReadDirectoryChangesW() needs to be called again
//
// There are, however, a lot of details that need to be handled correctly in order for this to work
//
// First of all, the callback passed to ReadDirectoryChangesW() will not be called unless the calling thread is in an alertable state. A thread
// is put into an alertable state with WaitForMultipleObjectsEx() (the Ex version is important since it has an extra parameter that lets you
// put the thread into an alertable state). Using this blocks the thread which means you need to create a worker thread in the background.
#if defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
#endif
#ifndef DRFSW_PRIVATE
#define DRFSW_PRIVATE static
#endif
// The number of FILE_NOTIFY_INFORMATION structures in the buffer that's passed to ReadDirectoryChangesW()
#define WIN32_RDC_FNI_COUNT DRFSW_EVENT_QUEUE_SIZE
#include <assert.h>
#if defined(_WIN32)
#include <windows.h>
DRFSW_PRIVATE void* drfsw_malloc(size_t sizeInBytes)
{
return HeapAlloc(GetProcessHeap(), 0, sizeInBytes);
}
DRFSW_PRIVATE void drfsw_free(void* p)
{
HeapFree(GetProcessHeap(), 0, p);
}
DRFSW_PRIVATE void drfsw_memcpy(void* dst, const void* src, size_t sizeInBytes)
{
CopyMemory(dst, src, sizeInBytes);
}
DRFSW_PRIVATE void drfsw_zeromemory(void* dst, size_t sizeInBytes)
{
ZeroMemory(dst, sizeInBytes);
}
#else
#include <stdlib.h>
#include <string.h>
DRFSW_PRIVATE void* drfsw_malloc(size_t sizeInBytes)
{
return malloc(sizeInBytes);
}
DRFSW_PRIVATE void drfsw_free(void* p)
{
free(p);
}
DRFSW_PRIVATE void drfsw_memcpy(void* dst, const void* src, size_t sizeInBytes)
{
memcpy(dst, src, sizeInBytes);
}
DRFSW_PRIVATE void drfsw_zeromemory(void* dst, size_t sizeInBytes)
{
memset(dst, 0, sizeInBytes);
}
#endif
DRFSW_PRIVATE int drfsw_strcpy(char* dst, unsigned int dstSizeInBytes, const char* src)
{
#if defined(_MSC_VER)
return strcpy_s(dst, dstSizeInBytes, src);
#else
if (dst == 0) {
return EINVAL;
}
if (dstSizeInBytes == 0) {
return ERANGE;
}
if (src == 0) {
dst[0] = '\0';
return EINVAL;
}
char* iDst = dst;
const char* iSrc = src;
size_t remainingSizeInBytes = dstSizeInBytes;
while (remainingSizeInBytes > 0 && iSrc[0] != '\0')
{
iDst[0] = iSrc[0];
iDst += 1;
iSrc += 1;
remainingSizeInBytes -= 1;
}
if (remainingSizeInBytes > 0) {
iDst[0] = '\0';
} else {
dst[0] = '\0';
return ERANGE;
}
return 0;
#endif
}
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)
{
if (pEvent != NULL)
{
pEvent->type = type;
if (absolutePath != NULL) {
drfsw_strcpy(pEvent->absolutePath, DRFSW_MAX_PATH, absolutePath);
} else {
drfsw_zeromemory(pEvent->absolutePath, DRFSW_MAX_PATH);
}
if (absolutePathNew != NULL) {
drfsw_strcpy(pEvent->absolutePathNew, DRFSW_MAX_PATH, absolutePathNew);
} else {
drfsw_zeromemory(pEvent->absolutePathNew, DRFSW_MAX_PATH);
}
if (absoluteBasePath != NULL) {
drfsw_strcpy(pEvent->absoluteBasePath, DRFSW_MAX_PATH, absoluteBasePath);
} else {
drfsw_zeromemory(pEvent->absoluteBasePath, DRFSW_MAX_PATH);
}
if (absoluteBasePathNew != NULL) {
drfsw_strcpy(pEvent->absoluteBasePathNew, DRFSW_MAX_PATH, absoluteBasePathNew);
} else {
drfsw_zeromemory(pEvent->absoluteBasePathNew, DRFSW_MAX_PATH);
}
return 1;
}
return 0;
}
typedef struct
{
// The buffer containing the events in the queue.
drfsw_event* pBuffer;
// The size of the buffer, in drfsw_event's.
unsigned int bufferSize;
// The number of items in the queue.
unsigned int count;
// The index of the first item in the queue.
unsigned int indexFirst;
#if defined(_WIN32)
// The semaphore for blocking in drfsw_next_event().
HANDLE hSemaphore;
// The mutex for synchronizing access to the buffer. This is needed because drfsw_next_event() will need to read the buffer while
// another thread is filling it with events. In addition, it will help to keep drfsw_next_event() and drfsw_peek_event() playing
// nicely with each other.
HANDLE hLock;
#endif
} drfsw_event_queue;
DRFSW_PRIVATE int drfsw_event_queue_init(drfsw_event_queue* pQueue)
{
if (pQueue != NULL)
{
pQueue->pBuffer = NULL;
pQueue->bufferSize = 0;
pQueue->indexFirst = 0;
pQueue->count = 0;
#if defined(_WIN32)
pQueue->hSemaphore = CreateSemaphoreW(NULL, 0, DRFSW_EVENT_QUEUE_SIZE, NULL);
if (pQueue->hSemaphore == NULL)
{
drfsw_free(pQueue->pBuffer);
return 0;
}
pQueue->hLock = CreateEventW(NULL, FALSE, TRUE, NULL);
if (pQueue->hLock == NULL)
{
CloseHandle(pQueue->hSemaphore);
drfsw_free(pQueue->pBuffer);
return 0;
}
#endif
return 1;
}
return 0;
}
DRFSW_PRIVATE void drfsw_event_queue_uninit(drfsw_event_queue* pQueue)
{
if (pQueue != NULL)
{
drfsw_free(pQueue->pBuffer);
pQueue->pBuffer = NULL;
pQueue->bufferSize = 0;
pQueue->indexFirst = 0;
pQueue->count = 0;
#if defined(_WIN32)
CloseHandle(pQueue->hSemaphore);
pQueue->hSemaphore = NULL;
CloseHandle(pQueue->hLock);
pQueue->hLock = NULL;
#endif
}
}
DRFSW_PRIVATE unsigned int drfsw_event_queue_getcount(drfsw_event_queue* pQueue)
{
if (pQueue != NULL)
{
return pQueue->count;
}
return 0;
}
DRFSW_PRIVATE void drfsw_event_queue_inflate(drfsw_event_queue* pQueue)
{
if (pQueue != NULL)
{
unsigned int newBufferSize = pQueue->bufferSize + 1;
if (pQueue->bufferSize > 0)
{
newBufferSize = pQueue->bufferSize*2;
}
drfsw_event* pOldBuffer = pQueue->pBuffer;
drfsw_event* pNewBuffer = (drfsw_event*)drfsw_malloc(newBufferSize * sizeof(drfsw_event));
for (unsigned int iDst = 0; iDst < pQueue->count; ++iDst)
{
unsigned int iSrc = (pQueue->indexFirst + iDst) % pQueue->bufferSize;
drfsw_memcpy(pNewBuffer + iDst, pOldBuffer + iSrc, sizeof(drfsw_event));
}
pQueue->bufferSize = newBufferSize;
pQueue->pBuffer = pNewBuffer;
pQueue->indexFirst = 0;
drfsw_free(pOldBuffer);
}
}
DRFSW_PRIVATE int drfsw_event_queue_pushback(drfsw_event_queue* pQueue, drfsw_event* pEvent)
{
if (pQueue != NULL)
{
if (pEvent != NULL)
{
unsigned int count = drfsw_event_queue_getcount(pQueue);
if (count == DRFSW_EVENT_QUEUE_SIZE)
{
// We've hit the limit.
return 0;
}
if (count == pQueue->bufferSize)
{
drfsw_event_queue_inflate(pQueue);
assert(count < pQueue->bufferSize);
}
// Insert the value.
unsigned int iDst = (pQueue->indexFirst + pQueue->count) % pQueue->bufferSize;
drfsw_memcpy(pQueue->pBuffer + iDst, pEvent, sizeof(drfsw_event));
// Increment the counter.
pQueue->count += 1;
return 1;
}
}
return 0;
}
DRFSW_PRIVATE int drfsw_event_queue_pop(drfsw_event_queue* pQueue, drfsw_event* pEventOut)
{
if (pQueue != NULL && pQueue->count > 0)
{
if (pEventOut != NULL)
{
drfsw_memcpy(pEventOut, pQueue->pBuffer + pQueue->indexFirst, sizeof(drfsw_event));
pQueue->indexFirst = (pQueue->indexFirst + 1) % pQueue->bufferSize;
}
pQueue->count -= 1;
return 1;
}
return 0;
}
// A simple function for appending a relative path to an absolute path. This does not resolve "." and ".." components.
DRFSW_PRIVATE int drfsw_make_absolute_path(const char* absolutePart, const char* relativePart, char absolutePathOut[DRFSW_MAX_PATH])
{
size_t absolutePartLength = strlen(absolutePart);
size_t relativePartLength = strlen(relativePart);
if (absolutePartLength > 0)
{
if (absolutePart[absolutePartLength - 1] == '/')
{
absolutePartLength -= 1;
}
if (absolutePartLength > DRFSW_MAX_PATH)
{
absolutePartLength = DRFSW_MAX_PATH - 1;
}
}
if (absolutePartLength + relativePartLength + 1 > DRFSW_MAX_PATH)
{
relativePartLength = DRFSW_MAX_PATH - 1 - absolutePartLength - 1; // -1 for the null terminate and -1 for the slash.
}
// Absolute part.
memcpy(absolutePathOut, absolutePart, absolutePartLength);
// Slash.
absolutePathOut[absolutePartLength] = '/';
// Relative part.
memcpy(absolutePathOut + absolutePartLength + 1, relativePart, relativePartLength);
// Null terminator.
absolutePathOut[absolutePartLength + 1 + relativePartLength] = '\0';
return 1;
}
// Replaces the back slashes with forward slashes in the given string. This operates on the string in place.
DRFSW_PRIVATE int drfsw_to_forward_slashes(char* path)
{
if (path != NULL)
{
unsigned int counter = 0;
while (*path++ != '\0' && counter++ < DRFSW_MAX_PATH)
{
if (*path == '\\')
{
*path = '/';
}
}
return 1;
}
return 0;
}
typedef struct
{
// A pointer to the buffer containing pointers to the objects.
void** buffer;
// The size of the buffer, in pointers.
unsigned int bufferSize;
// The number of pointers in the list.
unsigned int count;
} drfsw_list;
DRFSW_PRIVATE int drfsw_list_init(drfsw_list* pList)
{
if (pList != NULL)
{
pList->buffer = NULL;
pList->bufferSize = 0;
pList->count = 0;
return 1;
}
return 0;
}
DRFSW_PRIVATE void drfsw_list_uninit(drfsw_list* pList)
{
if (pList != NULL)
{
drfsw_free(pList->buffer);
}
}
DRFSW_PRIVATE void drfsw_list_inflate(drfsw_list* pList)
{
if (pList != NULL)
{
unsigned int newBufferSize = pList->bufferSize + 1;
if (pList->bufferSize > 0)
{
newBufferSize = pList->bufferSize*2;
}
void** pOldBuffer = pList->buffer;
void** pNewBuffer = (void**)drfsw_malloc(newBufferSize*sizeof(void*));
// Move everything over to the new buffer.
for (unsigned int i = 0; i < pList->count; ++i)
{
pNewBuffer[i] = pOldBuffer[i];
}
pList->bufferSize = newBufferSize;
pList->buffer = pNewBuffer;
}
}
DRFSW_PRIVATE void drfsw_list_pushback(drfsw_list* pList, void* pItem)
{
if (pList != NULL)
{
if (pList->count == pList->bufferSize)
{
drfsw_list_inflate(pList);
assert(pList->count < pList->bufferSize);
pList->buffer[pList->count] = pItem;
pList->count += 1;
}
}
}
DRFSW_PRIVATE void drfsw_list_removebyindex(drfsw_list* pList, unsigned int index)
{
if (pList != NULL)
{
assert(index < pList->count);
assert(pList->count > 0);
// Just move everything down one slot.
for (unsigned int i = index; index < pList->count - 1; ++i)
{
pList->buffer[i] = pList->buffer[i + 1];
}
pList->count -= 1;
}
}
#if defined(_WIN32)
#define DRFSW_MAX_PATH_W (DRFSW_MAX_PATH / sizeof(wchar_t))
///////////////////////////////////////////////
// ReadDirectoryChangesW
static const int WIN32_RDC_PENDING_WATCH = (1 << 0);
static const int WIN32_RDC_PENDING_DELETE = (1 << 1);
// Replaces the forward slashes to back slashes for use with Win32. This operates on the string in place.
DRFSW_PRIVATE int drfsw_to_back_slashes_wchar(wchar_t* path)
{
if (path != NULL)
{
unsigned int counter = 0;
while (*path++ != L'\0' && counter++ < DRFSW_MAX_PATH_W)
{
if (*path == L'/')
{
*path = L'\\';
}
}
return 1;
}
return 0;
}
// Converts a UTF-8 string to wchar_t for use with Win32. Free the returned pointer with drfsw_free().
DRFSW_PRIVATE int drfsw_utf8_to_wchar(const char* str, wchar_t wstrOut[DRFSW_MAX_PATH_W])
{
int wcharsWritten = MultiByteToWideChar(CP_UTF8, 0, str, -1, wstrOut, DRFSW_MAX_PATH_W);
if (wcharsWritten > 0)
{
assert((unsigned int)wcharsWritten <= DRFSW_MAX_PATH_W);
return 1;
}
return 0;
}
DRFSW_PRIVATE int drfsw_wchar_to_utf8(const wchar_t* wstr, int wstrCC, char pathOut[DRFSW_MAX_PATH])
{
int bytesWritten = WideCharToMultiByte(CP_UTF8, 0, wstr, wstrCC, pathOut, DRFSW_MAX_PATH - 1, NULL, NULL);
if (bytesWritten > 0)
{
assert((unsigned int)bytesWritten < DRFSW_MAX_PATH);
pathOut[bytesWritten] = '\0';
return 1;
}
return 0;
}
// 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().
DRFSW_PRIVATE int drfsw_to_win32_path_wchar(const char* path, wchar_t wpathOut[DRFSW_MAX_PATH_W])
{
if (drfsw_utf8_to_wchar(path, wpathOut))
{
return drfsw_to_back_slashes_wchar(wpathOut);
}
return 0;
}
// Converts a wchar_t Win32 path to a char unix style path (forward slashes instead of back).
DRFSW_PRIVATE int drfsw_from_win32_path(const wchar_t* wpath, int wpathCC, char pathOut[DRFSW_MAX_PATH])
{
if (drfsw_wchar_to_utf8(wpath, wpathCC, pathOut))
{
return drfsw_to_forward_slashes(pathOut);
}
return 0;
}
DRFSW_PRIVATE VOID CALLBACK drfsw_win32_completionroutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
DRFSW_PRIVATE VOID CALLBACK drfsw_win32_schedulewatchAPC(ULONG_PTR dwParam);
DRFSW_PRIVATE VOID CALLBACK drfsw_win32_cancelioAPC(ULONG_PTR dwParam);
typedef struct
{
// Thelist containing pointers to the watched directory objects. This is not thread safe.
drfsw_list list;
// The lock for synchronizing access to the list.
HANDLE hLock;
} drfsw_directory_list_win32;
// Structure representing the watcher context for the Win32 RDC method.
typedef struct
{
// The list of watched directories.
drfsw_directory_list_win32 watchedDirectories;
// The event queue.
drfsw_event_queue eventQueue;
// A handle to the watcher thread.
HANDLE hThread;
// The event that will become signaled when the watcher thread needs to be terminated.
HANDLE hTerminateEvent;
// The semaphore which is used when deleting a watched folder. This starts off at 0, and the maximum count is 1. When a watched
// directory is removed, the calling thread will wait on this semaphore while the worker thread does the deletion.
HANDLE hDeleteDirSemaphore;
// Whether or not the watch thread needs to be terminated.
BOOL terminateThread;
} drfsw_context_win32;
DRFSW_PRIVATE drfsw_context* drfsw_create_context_win32(void);
DRFSW_PRIVATE void drfsw_delete_context_win32(drfsw_context_win32* pContext);
DRFSW_PRIVATE int drfsw_add_directory_win32(drfsw_context_win32* pContext, const char* absolutePath);
DRFSW_PRIVATE void drfsw_remove_directory_win32(drfsw_context_win32* pContext, const char* absolutePath);
DRFSW_PRIVATE void drfsw_remove_all_directories_win32(drfsw_context_win32* pContext);
DRFSW_PRIVATE int drfsw_is_watching_directory_win32(drfsw_context_win32* pContext, const char* absolutePath);
DRFSW_PRIVATE int drfsw_next_event_win32(drfsw_context_win32* pContext, drfsw_event* pEventOut);
DRFSW_PRIVATE int drfsw_peek_event_win32(drfsw_context_win32* pContext, drfsw_event* pEventOut);
DRFSW_PRIVATE void drfsw_postevent_win32(drfsw_context_win32* pContext, drfsw_event* pEvent);
// Structure representing a directory that's being watched with the Win32 RDC method.
typedef struct
{
// A pointer to the context that owns this directory.
drfsw_context_win32* pContext;
// The absolute path of the directory being watched.
char absolutePath[DRFSW_MAX_PATH];
// The handle representing the directory. This is created with CreateFile() which means the directory itself will become locked
// because the operating system see's it as "in use". It is possible to modify the files and folder inside the directory, though.
HANDLE hDirectory;
// This is required for for ReadDirectoryChangesW().
OVERLAPPED overlapped;
// A pointer to the buffer containing the notification objects that is passed to the notification callback specified with
// ReadDirectoryChangesW(). This must be aligned to a DWORD boundary, but drfsw_malloc() will do that for us, so that should not
// be an issue.
FILE_NOTIFY_INFORMATION* pFNIBuffer1;
FILE_NOTIFY_INFORMATION* pFNIBuffer2;
// The size of the file notification information buffer, in bytes.
DWORD fniBufferSizeInBytes;
// Flags describing the state of the directory.
int flags;
} drfsw_directory_win32;
DRFSW_PRIVATE int drfsw_directory_win32_beginwatch(drfsw_directory_win32* pDirectory);
DRFSW_PRIVATE void drfsw_directory_win32_uninit(drfsw_directory_win32* pDirectory)
{
if (pDirectory != NULL)
{
if (pDirectory->hDirectory != NULL)
{
CloseHandle(pDirectory->hDirectory);
pDirectory->hDirectory = NULL;
}
drfsw_free(pDirectory->pFNIBuffer1);
pDirectory->pFNIBuffer1 = NULL;
drfsw_free(pDirectory->pFNIBuffer2);
pDirectory->pFNIBuffer2 = NULL;
}
}
DRFSW_PRIVATE int drfsw_directory_win32_init(drfsw_directory_win32* pDirectory, drfsw_context_win32* pContext, const char* absolutePath)
{
if (pDirectory != NULL)
{
pDirectory->pContext = pContext;
drfsw_zeromemory(pDirectory->absolutePath, DRFSW_MAX_PATH);
pDirectory->hDirectory = NULL;
pDirectory->pFNIBuffer1 = NULL;
pDirectory->pFNIBuffer2 = NULL;
pDirectory->fniBufferSizeInBytes = 0;
pDirectory->flags = 0;
size_t length = strlen(absolutePath);
if (length > 0)
{
memcpy(pDirectory->absolutePath, absolutePath, length);
pDirectory->absolutePath[length] = '\0';
wchar_t absolutePathWithBackSlashes[DRFSW_MAX_PATH_W];
if (drfsw_to_win32_path_wchar(absolutePath, absolutePathWithBackSlashes))
{
pDirectory->hDirectory = CreateFileW(
absolutePathWithBackSlashes,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
if (pDirectory->hDirectory != INVALID_HANDLE_VALUE)
{
// From MSDN:
//
// Using a completion routine. To receive notification through a completion routine, do not associate the directory with a
// completion port. Specify a completion routine in lpCompletionRoutine. This routine is called whenever the operation has
// been completed or canceled while the thread is in an alertable wait state. The hEvent member of the OVERLAPPED structure
// is not used by the system, so you can use it yourself.
ZeroMemory(&pDirectory->overlapped, sizeof(pDirectory->overlapped));
pDirectory->overlapped.hEvent = pDirectory;
pDirectory->fniBufferSizeInBytes = WIN32_RDC_FNI_COUNT * sizeof(FILE_NOTIFY_INFORMATION);
pDirectory->pFNIBuffer1 = (FILE_NOTIFY_INFORMATION*)drfsw_malloc(pDirectory->fniBufferSizeInBytes);
pDirectory->pFNIBuffer2 = (FILE_NOTIFY_INFORMATION*)drfsw_malloc(pDirectory->fniBufferSizeInBytes);
if (pDirectory->pFNIBuffer1 != NULL && pDirectory->pFNIBuffer2 != NULL)
{
// At this point the directory is initialized, however it is not yet being watched. The watch needs to be triggered from
// the worker thread. To do this, we need to signal hPendingWatchEvent, however that needs to be done after the context
// has added the directory to it's internal list.
return 1;
}
else
{
drfsw_directory_win32_uninit(pDirectory);
}
}
else
{
drfsw_directory_win32_uninit(pDirectory);
}
}
else
{
drfsw_directory_win32_uninit(pDirectory);
}
}
}
return 0;
}
DRFSW_PRIVATE int drfsw_directory_win32_schedulewatch(drfsw_directory_win32* pDirectory)
{
if (pDirectory != NULL)
{
pDirectory->flags |= WIN32_RDC_PENDING_WATCH;
QueueUserAPC(drfsw_win32_schedulewatchAPC, pDirectory->pContext->hThread, (ULONG_PTR)pDirectory);
return 1;
}
return 0;
}
DRFSW_PRIVATE int drfsw_directory_win32_scheduledelete(drfsw_directory_win32* pDirectory)
{
if (pDirectory != NULL)
{
pDirectory->flags |= WIN32_RDC_PENDING_DELETE;
QueueUserAPC(drfsw_win32_cancelioAPC, pDirectory->pContext->hThread, (ULONG_PTR)pDirectory);
return 1;
}
return 0;
}
DRFSW_PRIVATE int drfsw_directory_win32_beginwatch(drfsw_directory_win32* pDirectory)
{
// This function should only be called from the worker thread.
if (pDirectory != NULL)
{
DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION;
DWORD dwBytes = 0;
if (ReadDirectoryChangesW(pDirectory->hDirectory, pDirectory->pFNIBuffer1, pDirectory->fniBufferSizeInBytes, TRUE, dwNotifyFilter, &dwBytes, &pDirectory->overlapped, drfsw_win32_completionroutine))
{
pDirectory->flags &= ~WIN32_RDC_PENDING_WATCH;
return 1;
}
}
return 0;
}