diff --git a/src/core/filewatch.c b/src/core/filewatch.c index 79f02ef0..0bbb9187 100644 --- a/src/core/filewatch.c +++ b/src/core/filewatch.c @@ -53,17 +53,6 @@ typedef struct { int is_watching; } JanetWatcher; -/* Reject certain filename events without sending anything to the channel - * to make things faster and not waste time and memory creating events. This - * should also let us watch only certain file names, patterns, etc. */ -static int janet_watch_filter(JanetWatcher *watcher, Janet filename, int wd) { - /* TODO - add filtering */ - (void) watcher; - (void) filename; - (void) wd; - return 0; -} - #ifdef JANET_LINUX #include @@ -78,7 +67,7 @@ static const JanetWatchFlagName watcher_flags_linux[] = { {"create", IN_CREATE}, {"delete", IN_DELETE}, {"delete-self", IN_DELETE_SELF}, - {"ignored", IN_OPEN}, + {"ignored", IN_IGNORED}, {"modify", IN_MODIFY}, {"move-self", IN_MOVE_SELF}, {"moved-from", IN_MOVED_FROM}, @@ -153,7 +142,7 @@ static void janet_watcher_remove(JanetWatcher *watcher, const char *path) { static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { JanetStream *stream = fiber->ev_stream; - JanetWatcher *watcher = (JanetWatcher *) fiber->ev_state; + JanetWatcher *watcher = *((JanetWatcher **) fiber->ev_state); char buf[1024]; switch (event) { default: @@ -163,13 +152,11 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { break; case JANET_ASYNC_EVENT_CLOSE: janet_schedule(fiber, janet_wrap_nil()); - fiber->ev_state = NULL; janet_async_end(fiber); break; case JANET_ASYNC_EVENT_ERR: { janet_schedule(fiber, janet_wrap_nil()); - fiber->ev_state = NULL; janet_async_end(fiber); break; } @@ -221,9 +208,6 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { cursor += inevent.len; } - /* Filter events by pattern */ - if (!janet_watch_filter(watcher, name, inevent.wd)) continue; - /* Got an event */ Janet path = janet_table_get(watcher->watch_descriptors, janet_wrap_integer(inevent.wd)); JanetKV *event = janet_struct_begin(6); @@ -254,13 +238,17 @@ static void janet_watcher_listen(JanetWatcher *watcher) { watcher->is_watching = 1; JanetFunction *thunk = janet_thunk_delay(janet_wrap_nil()); JanetFiber *fiber = janet_fiber(thunk, 64, 0, NULL); - janet_async_start_fiber(fiber, watcher->stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, watcher); + JanetWatcher **state = janet_malloc(sizeof(JanetWatcher *)); /* Gross */ + *state = watcher; + janet_async_start_fiber(fiber, watcher->stream, JANET_ASYNC_LISTEN_READ, watcher_callback_read, state); + janet_gcroot(janet_wrap_abstract(watcher)); } static void janet_watcher_unlisten(JanetWatcher *watcher) { if (!watcher->is_watching) return; watcher->is_watching = 0; janet_stream_close(watcher->stream); + janet_gcunroot(janet_wrap_abstract(watcher)); } #elif JANET_WINDOWS @@ -268,6 +256,16 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) { #define WATCHFLAG_RECURSIVE 0x100000u static const JanetWatchFlagName watcher_flags_windows[] = { + {"all", + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_CREATION | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_LAST_ACCESS | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_SECURITY | + FILE_NOTIFY_CHANGE_SIZE | + WATCHFLAG_RECURSIVE}, {"attributes", FILE_NOTIFY_CHANGE_ATTRIBUTES}, {"creation", FILE_NOTIFY_CHANGE_CREATION}, {"dir-name", FILE_NOTIFY_CHANGE_DIR_NAME}, @@ -392,7 +390,7 @@ static void watcher_callback_read(JanetFiber *fiber, JanetAsyncEvent event) { } JanetKV *event = janet_struct_begin(3); - janet_struct_put(event, janet_ckeywordv("action"), janet_ckeywordv(watcher_actions_windows[fni->Action])); + janet_struct_put(event, janet_ckeywordv("type"), janet_ckeywordv(watcher_actions_windows[fni->Action])); janet_struct_put(event, janet_ckeywordv("file-name"), filename); janet_struct_put(event, janet_ckeywordv("dir"), janet_wrap_string(ow->dir_path)); Janet eventv = janet_wrap_struct(janet_struct_end(event)); @@ -470,6 +468,7 @@ static void janet_watcher_listen(JanetWatcher *watcher) { OverlappedWatch *ow = janet_unwrap_pointer(kv->value); start_listening_ow(ow); } + janet_gcroot(janet_wrap_abstract(watcher)); } static void janet_watcher_unlisten(JanetWatcher *watcher) { @@ -482,6 +481,7 @@ static void janet_watcher_unlisten(JanetWatcher *watcher) { janet_stream_close(ow->stream); } janet_table_clear(watcher->watch_descriptors); + janet_gcunroot(janet_wrap_abstract(watcher)); } #else diff --git a/test/suite-filewatch.janet b/test/suite-filewatch.janet index 07883119..c4718a52 100644 --- a/test/suite-filewatch.janet +++ b/test/suite-filewatch.janet @@ -24,6 +24,8 @@ (assert true) (def chan (ev/chan 1000)) +(def is-win (= :windows (os/which))) +(def is-linux (= :linux (os/which))) # Test GC (assert-no-error "filewatch/new" (filewatch/new chan)) @@ -36,7 +38,7 @@ (def event (ev/take chan)) (when is-verbose (pp event)) (assert event "check event") - (assert (= value (get event key)) (string/format "got %p, exepcted %p" (get event key) value)))) + (assert (= value (get event key)) (string/format "got %p, expected %p" (get event key) value)))) (defn- expect-empty [] @@ -49,57 +51,121 @@ (def path (string dir "/" name)) (spit path "test text")) +# Different operating systems report events differently. While it would be nice to +# normalize this, each system has very large limitations in what can be reported when +# compared with other systems. As such, the maximum subset of common functionality here +# is quite small. Instead, test the capabilities of each system. + # Create a file watcher on two test directories (def fw (filewatch/new chan)) (def td1 (randdir)) (def td2 (randdir)) +(rmrf td1) +(rmrf td2) (os/mkdir td1) (os/mkdir td2) -(filewatch/add fw td1 :last-write :last-access :file-name :dir-name :size :attributes :recursive) -(filewatch/add fw td2 :last-write :last-access :file-name :dir-name :size :attributes) +(case (os/which) + :windows + (do + (filewatch/add fw td1 :last-write :last-access :file-name :dir-name :size :attributes :recursive) + (filewatch/add fw td2 :last-write :last-access :file-name :dir-name :size :attributes)) + # default + (do + (filewatch/add fw td1 :close-write :create :delete) + (filewatch/add fw td2 :close-write :create :delete :ignored))) (assert-no-error "filewatch/listen no error" (filewatch/listen fw)) -# Simulate some file event -(spit-file td1 "file1.txt") -(expect :action :added) -(expect :action :modified) -(expect-empty) -(gccollect) -(spit-file td1 "file1.txt") -(expect :action :modified) -(expect :action :modified) -(expect-empty) -(gccollect) +# +# Windows file writing +# -# Check td2 -(spit-file td2 "file2.txt") -(expect :action :added) -(expect :action :modified) -(expect-empty) +(when is-win + (spit-file td1 "file1.txt") + (expect :type :added) + (expect :type :modified) + (expect-empty) + (gccollect) + (spit-file td1 "file1.txt") + (expect :type :modified) + (expect :type :modified) + (expect-empty) + (gccollect) -# Remove a file, then wait for remove event -(rmrf (string td1 "/file1.txt")) -(expect :action :removed) -(expect-empty) + # Check td2 + (spit-file td2 "file2.txt") + (expect :type :added) + (expect :type :modified) + (expect-empty) -# Unlisten to some events -(filewatch/remove fw td2) + # Remove a file, then wait for remove event + (rmrf (string td1 "/file1.txt")) + (expect :type :removed) + (expect-empty) -# Check that we don't get anymore events from test directory 2 -(spit-file td2 "file2.txt") -(expect-empty) + # Unlisten to some events + (filewatch/remove fw td2) -# Repeat and things should still work with test directory 1 -(spit-file td1 "file1.txt") -(expect :action :added) -(expect :action :modified) -(expect-empty) -(gccollect) -(spit-file td1 "file1.txt") -(expect :action :modified) -(expect :action :modified) -(expect-empty) -(gccollect) + # Check that we don't get anymore events from test directory 2 + (spit-file td2 "file2.txt") + (expect-empty) + + # Repeat and things should still work with test directory 1 + (spit-file td1 "file1.txt") + (expect :type :added) + (expect :type :modified) + (expect-empty) + (gccollect) + (spit-file td1 "file1.txt") + (expect :type :modified) + (expect :type :modified) + (expect-empty) + (gccollect)) + +# +# Linux file writing +# + +(when is-linux + (spit-file td1 "file1.txt") + (expect :type :create) + (expect :type :close-write) + (expect-empty) + (gccollect) + (spit-file td1 "file1.txt") + (expect :type :close-write) + (expect-empty) + (gccollect) + + # Check td2 + (spit-file td2 "file2.txt") + (expect :type :create) + (expect :type :close-write) + (expect-empty) + + # Remove a file, then wait for remove event + (rmrf (string td1 "/file1.txt")) + (expect :type :delete) + (expect-empty) + + # Unlisten to some events + (filewatch/remove fw td2) + (expect :type :ignored) + (expect-empty) + + # Check that we don't get anymore events from test directory 2 + (spit-file td2 "file2.txt") + (expect-empty) + + # Repeat and things should still work with test directory 1 + (spit-file td1 "file1.txt") + (expect :type :create) + (expect :type :close-write) + (expect-empty) + (gccollect) + (spit-file td1 "file1.txt") + (expect :type :close-write) + (expect-empty) + (gccollect)) (assert-no-error "filewatch/unlisten no error" (filewatch/unlisten fw)) (assert-no-error "cleanup 1" (rmrf td1))