From e303f7ff93088cd1699f5b4a3b0ada3f197cefd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Thu, 2 Jul 2015 01:04:11 +0200 Subject: [PATCH] win: add support for recursive file watching Original patch by @ghostoy, modified by @bpasero and yours truly. Refs: https://github.com/joyent/libuv/pull/1473 Refs: https://github.com/libuv/libuv/pull/198 PR-URL: https://github.com/libuv/libuv/pull/421 Reviewed-By: Bert Belder --- docs/src/fs_event.rst | 2 +- src/win/fs-event.c | 22 +++++++-- src/win/internal.h | 2 +- test/test-fs-event.c | 110 ++++++++++++++++++++++++++++++++++++++++++ test/test-list.h | 2 + 5 files changed, 133 insertions(+), 5 deletions(-) diff --git a/docs/src/fs_event.rst b/docs/src/fs_event.rst index 681ae52f95c..c2d7f520236 100644 --- a/docs/src/fs_event.rst +++ b/docs/src/fs_event.rst @@ -88,7 +88,7 @@ API `path` for changes. `flags` can be an ORed mask of :c:type:`uv_fs_event_flags`. .. note:: Currently the only supported flag is ``UV_FS_EVENT_RECURSIVE`` and - only on OSX. + only on OSX and Windows. .. c:function:: int uv_fs_event_stop(uv_fs_event_t* handle) diff --git a/src/win/fs-event.c b/src/win/fs-event.c index eb205d3921d..ba68f78c82a 100644 --- a/src/win/fs-event.c +++ b/src/win/fs-event.c @@ -43,7 +43,7 @@ static void uv_fs_event_queue_readdirchanges(uv_loop_t* loop, if (!ReadDirectoryChangesW(handle->dir_handle, handle->buffer, uv_directory_watcher_buffer_size, - FALSE, + (handle->flags & UV_FS_EVENT_RECURSIVE) ? TRUE : FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | @@ -63,6 +63,20 @@ static void uv_fs_event_queue_readdirchanges(uv_loop_t* loop, handle->req_pending = 1; } +static int uv_relative_path(const WCHAR* filename, + const WCHAR* dir, + WCHAR** relpath) { + int dirlen = wcslen(dir); + int filelen = wcslen(filename); + if (dir[dirlen - 1] == '\\') + dirlen--; + *relpath = uv__malloc((MAX_PATH + 1) * sizeof(WCHAR)); + if (!*relpath) + uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc"); + wcsncpy(*relpath, filename + dirlen + 1, filelen - dirlen - 1); + (*relpath)[filelen - dirlen - 1] = L'\0'; + return 0; +} static int uv_split_path(const WCHAR* filename, WCHAR** dir, WCHAR** file) { @@ -237,7 +251,7 @@ int uv_fs_event_start(uv_fs_event_t* handle, if (!ReadDirectoryChangesW(handle->dir_handle, handle->buffer, uv_directory_watcher_buffer_size, - FALSE, + (flags & UV_FS_EVENT_RECURSIVE) ? TRUE : FALSE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | @@ -410,7 +424,9 @@ void uv_process_fs_event_req(uv_loop_t* loop, uv_req_t* req, if (long_filenamew) { /* Get the file name out of the long path. */ - result = uv_split_path(long_filenamew, NULL, &filenamew); + result = uv_relative_path(long_filenamew, + handle->dirw, + &filenamew); uv__free(long_filenamew); if (result == 0) { diff --git a/src/win/internal.h b/src/win/internal.h index 7ee8fa43c5d..8d4081bdb51 100644 --- a/src/win/internal.h +++ b/src/win/internal.h @@ -64,7 +64,7 @@ extern UV_THREAD_LOCAL int uv__crt_assert_enabled; /* Used by all handles. */ #define UV_HANDLE_CLOSED 0x00000002 -#define UV_HANDLE_ENDGAME_QUEUED 0x00000004 +#define UV_HANDLE_ENDGAME_QUEUED 0x00000008 /* uv-common.h: #define UV__HANDLE_CLOSING 0x00000001 */ /* uv-common.h: #define UV__HANDLE_ACTIVE 0x00000040 */ diff --git a/test/test-fs-event.c b/test/test-fs-event.c index 0a2ba331455..178ada85517 100644 --- a/test/test-fs-event.c +++ b/test/test-fs-event.c @@ -37,6 +37,7 @@ static uv_fs_event_t fs_event; static const char file_prefix[] = "fsevent-"; +static const char file_prefix_in_subdir[] = "subdir"; static uv_timer_t timer; static int timer_cb_called; static int close_cb_called; @@ -51,6 +52,7 @@ static char fs_event_filename[1024]; static int timer_cb_touch_called; static void fs_event_unlink_files(uv_timer_t* handle); +static void fs_event_unlink_files_in_subdir(uv_timer_t* handle); static void create_dir(uv_loop_t* loop, const char* name) { int r; @@ -138,6 +140,25 @@ static void fs_event_cb_dir_multi_file(uv_fs_event_t* handle, } } +static void fs_event_cb_dir_multi_file_in_subdir(uv_fs_event_t* handle, + const char* filename, + int events, + int status) { + fs_event_cb_called++; + ASSERT(handle == &fs_event); + ASSERT(status == 0); + ASSERT(events == UV_RENAME || events == UV_CHANGE); + ASSERT(filename == NULL || + strncmp(filename, file_prefix_in_subdir, sizeof(file_prefix_in_subdir) - 1) == 0); + + /* Stop watching dir when received events about all files: + * both create and close events */ + if (fs_event_cb_called == 2 * fs_event_file_count) { + ASSERT(0 == uv_fs_event_stop(handle)); + uv_close((uv_handle_t*) handle, close_cb); + } +} + static const char* fs_event_get_filename(int i) { snprintf(fs_event_filename, sizeof(fs_event_filename), @@ -147,6 +168,15 @@ static const char* fs_event_get_filename(int i) { return fs_event_filename; } +static const char* fs_event_get_filename_in_subdir(int i) { + snprintf(fs_event_filename, + sizeof(fs_event_filename), + "watch_dir/subdir/%s%d", + file_prefix, + i); + return fs_event_filename; +} + static void fs_event_create_files(uv_timer_t* handle) { int i; @@ -164,6 +194,41 @@ static void fs_event_create_files(uv_timer_t* handle) { ASSERT(0 == uv_timer_start(&timer, fs_event_unlink_files, 50, 0)); } +static void fs_event_create_files_in_subdir(uv_timer_t* handle) { + int i; + + /* Already created all files */ + if (fs_event_created == fs_event_file_count) { + uv_close((uv_handle_t*) &timer, close_cb); + return; + } + + /* Create all files */ + for (i = 0; i < 16; i++, fs_event_created++) + create_file(handle->loop, fs_event_get_filename_in_subdir(i)); + + /* And unlink them */ + ASSERT(0 == uv_timer_start(&timer, fs_event_unlink_files_in_subdir, 50, 0)); +} + +void fs_event_unlink_files_in_subdir(uv_timer_t* handle) { + int r; + int i; + + /* NOTE: handle might be NULL if invoked not as timer callback */ + + /* Unlink all files */ + for (i = 0; i < 16; i++) { + r = remove(fs_event_get_filename_in_subdir(i)); + if (handle != NULL) + ASSERT(r == 0); + } + + /* And create them again */ + if (handle != NULL) + ASSERT(0 == uv_timer_start(&timer, fs_event_create_files_in_subdir, 50, 0)); +} + void fs_event_unlink_files(uv_timer_t* handle) { int r; int i; @@ -282,6 +347,51 @@ TEST_IMPL(fs_event_watch_dir) { return 0; } +TEST_IMPL(fs_event_watch_dir_recursive) { +#if defined(__APPLE__) || defined(_WIN32) + uv_loop_t* loop; + int r; + + /* Setup */ + loop = uv_default_loop(); + fs_event_unlink_files(NULL); + remove("watch_dir/file2"); + remove("watch_dir/file1"); + remove("watch_dir/subdir"); + remove("watch_dir/"); + create_dir(loop, "watch_dir"); + create_dir(loop, "watch_dir/subdir"); + + r = uv_fs_event_init(loop, &fs_event); + ASSERT(r == 0); + r = uv_fs_event_start(&fs_event, fs_event_cb_dir_multi_file_in_subdir, "watch_dir", UV_FS_EVENT_RECURSIVE); + ASSERT(r == 0); + r = uv_timer_init(loop, &timer); + ASSERT(r == 0); + r = uv_timer_start(&timer, fs_event_create_files_in_subdir, 100, 0); + ASSERT(r == 0); + + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT(fs_event_cb_called == 2 * fs_event_file_count); + ASSERT(fs_event_created == fs_event_file_count); + ASSERT(close_cb_called == 2); + + /* Cleanup */ + fs_event_unlink_files_in_subdir(NULL); + remove("watch_dir/file2"); + remove("watch_dir/file1"); + remove("watch_dir/subdir"); + remove("watch_dir/"); + + MAKE_VALGRIND_HAPPY(); + return 0; +#else + RETURN_SKIP("Recursive directory watching not supported on this platform."); +#endif +} + + TEST_IMPL(fs_event_watch_file) { uv_loop_t* loop = uv_default_loop(); int r; diff --git a/test/test-list.h b/test/test-list.h index 872265b4cf8..fec833068ad 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -258,6 +258,7 @@ TEST_DECLARE (fs_file_open_append) TEST_DECLARE (fs_stat_missing_path) TEST_DECLARE (fs_read_file_eof) TEST_DECLARE (fs_event_watch_dir) +TEST_DECLARE (fs_event_watch_dir_recursive) TEST_DECLARE (fs_event_watch_file) TEST_DECLARE (fs_event_watch_file_twice) TEST_DECLARE (fs_event_watch_file_current_dir) @@ -664,6 +665,7 @@ TASK_LIST_START TEST_ENTRY (fs_read_file_eof) TEST_ENTRY (fs_file_open_append) TEST_ENTRY (fs_event_watch_dir) + TEST_ENTRY (fs_event_watch_dir_recursive) TEST_ENTRY (fs_event_watch_file) TEST_ENTRY (fs_event_watch_file_twice) TEST_ENTRY (fs_event_watch_file_current_dir)