From 1e0757ffda47c625b0f7feeeb91c399864787f9e Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Fri, 16 Sep 2011 12:04:36 -0700 Subject: [PATCH] windows: file watcher --- include/uv-private/uv-win.h | 13 +- include/uv.h | 38 +++- src/win/fs-event.c | 382 ++++++++++++++++++++++++++++++++++++ src/win/handle.c | 8 + src/win/internal.h | 8 + src/win/req.c | 4 + test/test-fs-event.c | 215 ++++++++++++++++++++ test/test-list.h | 6 + uv.gyp | 3 + 9 files changed, 675 insertions(+), 2 deletions(-) create mode 100644 src/win/fs-event.c create mode 100644 test/test-fs-event.c diff --git a/include/uv-private/uv-win.h b/include/uv-private/uv-win.h index c43313f7b2..f203d67289 100644 --- a/include/uv-private/uv-win.h +++ b/include/uv-private/uv-win.h @@ -86,7 +86,8 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); UV_GETADDRINFO_REQ, \ UV_PROCESS_EXIT, \ UV_PROCESS_CLOSE, \ - UV_UDP_RECV + UV_UDP_RECV, \ + UV_FS_EVENT_REQ #define UV_REQ_PRIVATE_FIELDS \ union { \ @@ -261,6 +262,16 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); #define UV_WORK_PRIVATE_FIELDS \ +#define UV_FS_EVENT_PRIVATE_FIELDS \ + struct uv_fs_event_req_s { \ + UV_REQ_FIELDS \ + } req; \ + HANDLE dir_handle; \ + int req_pending; \ + uv_fs_event_cb cb; \ + wchar_t* filew; \ + int is_path_dir; \ + char* buffer; #define UV_TTY_PRIVATE_FIELDS /* empty */ diff --git a/include/uv.h b/include/uv.h index 4741616595..98328a2272 100644 --- a/include/uv.h +++ b/include/uv.h @@ -65,6 +65,8 @@ typedef struct uv_write_s uv_write_t; typedef struct uv_connect_s uv_connect_t; typedef struct uv_udp_send_s uv_udp_send_t; typedef struct uv_fs_s uv_fs_t; +/* uv_fs_event_t is a subclass of uv_handle_t. */ +typedef struct uv_fs_event_s uv_fs_event_t; typedef struct uv_work_s uv_work_t; #if defined(__unix__) || defined(__POSIX__) || defined(__APPLE__) @@ -137,6 +139,15 @@ typedef void (*uv_fs_cb)(uv_fs_t* req); typedef void (*uv_work_cb)(uv_work_t* req); typedef void (*uv_after_work_cb)(uv_work_t* req); +/* +* This will be called repeatedly after the uv_fs_event_t is initialized. +* If uv_fs_event_t was initialized with a directory the filename parameter +* will be a relative path to a file contained in the directory. +* The events paramenter is an ORed mask of enum uv_fs_event elements. +*/ +typedef void (*uv_fs_event_cb)(uv_fs_event_t* handle, const char* filename, + int events, int status); + /* Expand this list if necessary. */ typedef enum { @@ -201,7 +212,8 @@ typedef enum { UV_ASYNC, UV_ARES_TASK, UV_ARES_EVENT, - UV_PROCESS + UV_PROCESS, + UV_FS_EVENT } uv_handle_type; typedef enum { @@ -1002,6 +1014,27 @@ int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file file, int uid, int gid, uv_fs_cb cb); +enum uv_fs_event { + UV_RENAME = 1, + UV_CHANGE = 2 +}; + + +struct uv_fs_event_s { + UV_HANDLE_FIELDS + char* filename; + UV_FS_EVENT_PRIVATE_FIELDS +}; + + +/* +* If filename is a directory then we will watch for all events in that +* directory. If filename is a file - we will only get events from that +* file. Subdirectories are not watched. +*/ +int uv_fs_event_init(uv_loop_t* loop, uv_fs_event_t* handle, + const char* filename, uv_fs_event_cb cb); + /* Utility */ /* Convert string ip addresses to binary structures */ @@ -1037,6 +1070,7 @@ union uv_any_handle { uv_async_t async; uv_timer_t timer; uv_getaddrinfo_t getaddrinfo; + uv_fs_event_t fs_event; }; union uv_any_req { @@ -1064,6 +1098,7 @@ struct uv_counters_s { uint64_t async_init; uint64_t timer_init; uint64_t process_init; + uint64_t fs_event_init; }; @@ -1097,6 +1132,7 @@ struct uv_loop_s { #undef UV_GETADDRINFO_PRIVATE_FIELDS #undef UV_FS_REQ_PRIVATE_FIELDS #undef UV_WORK_PRIVATE_FIELDS +#undef UV_FS_EVENT_PRIVATE_FIELDS #ifdef __cplusplus } diff --git a/src/win/fs-event.c b/src/win/fs-event.c new file mode 100644 index 0000000000..8c4de2621d --- /dev/null +++ b/src/win/fs-event.c @@ -0,0 +1,382 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include "uv.h" +#include "internal.h" + + +const unsigned int uv_directory_watcher_buffer_size = 4096; + + +static void uv_fs_event_init_handle(uv_loop_t* loop, uv_fs_event_t* handle, + const char* filename, uv_fs_event_cb cb) { + handle->type = UV_FS_EVENT; + handle->loop = loop; + handle->flags = 0; + handle->cb = cb; + handle->is_path_dir = 0; + handle->dir_handle = INVALID_HANDLE_VALUE; + handle->buffer = NULL; + handle->req_pending = 0; + handle->filew = NULL; + + uv_req_init(loop, (uv_req_t*)&handle->req); + handle->req.type = UV_FS_EVENT_REQ; + handle->req.data = (void*)handle; + + handle->filename = strdup(filename); + if (!handle->filename) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + + loop->counters.handle_init++; + loop->counters.fs_event_init++; + + uv_ref(loop); +} + + +static void uv_fs_event_queue_readdirchanges(uv_loop_t* loop, + uv_fs_event_t* handle) { + assert(handle->dir_handle != INVALID_HANDLE_VALUE); + assert(!handle->req_pending); + + memset(&(handle->req.overlapped), 0, sizeof(handle->req.overlapped)); + if (!ReadDirectoryChangesW(handle->dir_handle, + handle->buffer, + uv_directory_watcher_buffer_size, + FALSE, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_LAST_ACCESS | + FILE_NOTIFY_CHANGE_CREATION | + FILE_NOTIFY_CHANGE_SECURITY, + NULL, + &handle->req.overlapped, + NULL)) { + /* Make this req pending reporting an error. */ + SET_REQ_ERROR(&handle->req, GetLastError()); + uv_insert_pending_req(loop, (uv_req_t*)&handle->req); + } + + handle->req_pending = 1; +} + + +static int uv_split_path(const wchar_t* filename, wchar_t** dir, + wchar_t** file) { + int len = wcslen(filename); + int i = len; + while (i > 0 && filename[--i] != '\\' && filename[i] != '/'); + + if (i == 0) { + *dir = (wchar_t*)malloc((MAX_PATH + 1) * sizeof(wchar_t)); + if (!*dir) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + + if (!GetCurrentDirectoryW(MAX_PATH, *dir)) { + free(*dir); + *dir = NULL; + return -1; + } + + *file = wcsdup(filename); + } else { + *dir = (wchar_t*)malloc((i + 1) * sizeof(wchar_t)); + if (!*dir) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + wcsncpy(*dir, filename, i); + (*dir)[i] = L'\0'; + + *file = (wchar_t*)malloc((len - i) * sizeof(wchar_t)); + if (!*file) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + wcsncpy(*file, filename + i + 1, len - i - 1); + (*file)[len - i - 1] = L'\0'; + } + + return 0; +} + + +int uv_fs_event_init(uv_loop_t* loop, uv_fs_event_t* handle, + const char* filename, uv_fs_event_cb cb) { + int name_size; + DWORD attr, last_error; + wchar_t* dir = NULL, *dir_to_watch, *filenamew; + + uv_fs_event_init_handle(loop, handle, filename, cb); + + /* Convert name to UTF16. */ + name_size = uv_utf8_to_utf16(filename, NULL, 0) * sizeof(wchar_t); + filenamew = (wchar_t*)malloc(name_size); + if (!filenamew) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + + if (!uv_utf8_to_utf16(filename, filenamew, + name_size / sizeof(wchar_t))) { + uv_set_sys_error(loop, GetLastError()); + return -1; + } + + /* Determine whether filename is a file or a directory. */ + attr = GetFileAttributesW(filenamew); + if (attr == INVALID_FILE_ATTRIBUTES) { + last_error = GetLastError(); + goto error; + } + + handle->is_path_dir = (attr & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 0; + + if (handle->is_path_dir) { + /* filename is a directory, so that's the directory that we will watch. */ + dir_to_watch = filenamew; + } else { + /* + * filename is a file. So we split filename into dir & file parts, and + * watch the dir directory. + */ + if (uv_split_path(filenamew, &dir, &handle->filew) != 0) { + last_error = GetLastError(); + goto error; + } + + dir_to_watch = dir; + } + + handle->dir_handle = CreateFileW(dir_to_watch, + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_DELETE | + FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OVERLAPPED, + NULL); + + if (dir) { + free(dir); + dir = NULL; + } + + if (handle->dir_handle == INVALID_HANDLE_VALUE) { + last_error = GetLastError(); + goto error; + } + + if (CreateIoCompletionPort(handle->dir_handle, + loop->iocp, + (ULONG_PTR)handle, + 0) == NULL) { + last_error = GetLastError(); + goto error; + } + + handle->buffer = (char*)_aligned_malloc(uv_directory_watcher_buffer_size, + sizeof(DWORD)); + if (!handle->buffer) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + + memset(&(handle->req.overlapped), 0, sizeof(handle->req.overlapped)); + + if (!ReadDirectoryChangesW(handle->dir_handle, + handle->buffer, + uv_directory_watcher_buffer_size, + FALSE, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_LAST_ACCESS | + FILE_NOTIFY_CHANGE_CREATION | + FILE_NOTIFY_CHANGE_SECURITY, + NULL, + &handle->req.overlapped, + NULL)) { + last_error = GetLastError(); + goto error; + } + + handle->req_pending = 1; + return 0; + +error: + if (handle->filename) { + free(handle->filename); + handle->filename = NULL; + } + + if (handle->filew) { + free(handle->filew); + handle->filew = NULL; + } + + if (handle->dir_handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle->dir_handle); + handle->dir_handle = INVALID_HANDLE_VALUE; + } + + if (handle->buffer) { + _aligned_free(handle->buffer); + handle->buffer = NULL; + } + + uv_set_sys_error(loop, last_error); + return -1; +} + + +void uv_process_fs_event_req(uv_loop_t* loop, uv_req_t* req, + uv_fs_event_t* handle) { + FILE_NOTIFY_INFORMATION* file_info; + char* filename = NULL; + int utf8size; + DWORD offset = 0; + + assert(req->type == UV_FS_EVENT_REQ); + assert(handle->req_pending); + handle->req_pending = 0; + + if (REQ_SUCCESS(req) && req->overlapped.InternalHigh > 0) { + do { + file_info = (FILE_NOTIFY_INFORMATION*)(handle->buffer + offset); + + /* + * Fire the event only if we were asked to watch a directory, + * or if the filename filter matches. + */ + if (handle->is_path_dir || _wcsnicmp(handle->filew, file_info->FileName, + file_info->FileNameLength / sizeof(wchar_t)) == 0) { + + /* Convert the filename to utf8. */ + utf8size = uv_utf16_to_utf8(file_info->FileName, + file_info->FileNameLength / + sizeof(wchar_t), + NULL, + 0); + if (utf8size) { + filename = (char*)malloc(utf8size + 1); + if (!filename) { + uv_fatal_error(ERROR_OUTOFMEMORY, "malloc"); + } + + utf8size = uv_utf16_to_utf8(file_info->FileName, + file_info->FileNameLength / + sizeof(wchar_t), + filename, + utf8size); + if (utf8size) { + filename[utf8size] = L'\0'; + } else { + free(filename); + filename = NULL; + } + } + + switch (file_info->Action) { + case FILE_ACTION_ADDED: + case FILE_ACTION_REMOVED: + case FILE_ACTION_RENAMED_OLD_NAME: + case FILE_ACTION_RENAMED_NEW_NAME: + handle->cb(handle, filename, UV_RENAME, 0); + break; + + case FILE_ACTION_MODIFIED: + handle->cb(handle, filename, UV_CHANGE, 0); + break; + } + + free(filename); + filename = NULL; + } + + offset = file_info->NextEntryOffset; + } while(offset); + } else { + /* + * TODO: InternalHigh == 0 indicates overflow. + * Fire the appropriate event once we figure out the api. + */ + loop->last_error = GET_REQ_UV_ERROR(req); + handle->cb(handle, NULL, -1, -1); + } + + if (!(handle->flags & UV_HANDLE_CLOSING)) { + uv_fs_event_queue_readdirchanges(loop, handle); + } else { + uv_want_endgame(loop, (uv_handle_t*)handle); + } +} + + +void uv_fs_event_close(uv_loop_t* loop, uv_fs_event_t* handle) { + if (handle->dir_handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle->dir_handle); + handle->dir_handle = INVALID_HANDLE_VALUE; + } + + if (!handle->req_pending) { + uv_want_endgame(loop, (uv_handle_t*)handle); + } +} + + +void uv_fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle) { + if (handle->flags & UV_HANDLE_CLOSING && + !handle->req_pending) { + assert(!(handle->flags & UV_HANDLE_CLOSED)); + handle->flags |= UV_HANDLE_CLOSED; + + if (handle->buffer) { + _aligned_free(handle->buffer); + handle->buffer = NULL; + } + + if (handle->filew) { + free(handle->filew); + handle->filew = NULL; + } + + if (handle->filename) { + free(handle->filename); + handle->filename = NULL; + } + + if (handle->close_cb) { + handle->close_cb((uv_handle_t*)handle); + } + + uv_unref(loop); + } +} diff --git a/src/win/handle.c b/src/win/handle.c index 3d32a403e4..ab4f64bc5a 100644 --- a/src/win/handle.c +++ b/src/win/handle.c @@ -120,6 +120,10 @@ void uv_close(uv_handle_t* handle, uv_close_cb cb) { uv_process_close(loop, process); return; + case UV_FS_EVENT: + uv_fs_event_close(loop, (uv_fs_event_t*)handle); + return; + default: /* Not supported */ abort(); @@ -177,6 +181,10 @@ void uv_process_endgames(uv_loop_t* loop) { uv_process_endgame(loop, (uv_process_t*) handle); break; + case UV_FS_EVENT: + uv_fs_event_endgame(loop, (uv_fs_event_t*) handle); + break; + default: assert(0); break; diff --git a/src/win/internal.h b/src/win/internal.h index ee1834ed6d..87a64eda1b 100644 --- a/src/win/internal.h +++ b/src/win/internal.h @@ -231,6 +231,14 @@ void uv_process_fs_req(uv_loop_t* loop, uv_fs_t* req); void uv_process_work_req(uv_loop_t* loop, uv_work_t* req); +/* + * FS Event + */ +void uv_process_fs_event_req(uv_loop_t* loop, uv_req_t* req, uv_fs_event_t* handle); +void uv_fs_event_close(uv_loop_t* loop, uv_fs_event_t* handle); +void uv_fs_event_endgame(uv_loop_t* loop, uv_fs_event_t* handle); + + /* * Error handling */ diff --git a/src/win/req.c b/src/win/req.c index beff95bd0f..a0a6e03dc6 100644 --- a/src/win/req.c +++ b/src/win/req.c @@ -165,6 +165,10 @@ void uv_process_reqs(uv_loop_t* loop) { uv_process_work_req(loop, (uv_work_t*) req); break; + case UV_FS_EVENT_REQ: + uv_process_fs_event_req(loop, req, (uv_fs_event_t*) req->data); + break; + default: assert(0); } diff --git a/test/test-fs-event.c b/test/test-fs-event.c new file mode 100644 index 0000000000..70b1f50e1c --- /dev/null +++ b/test/test-fs-event.c @@ -0,0 +1,215 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "task.h" +#include + +uv_fs_event_t fs_event; +uv_timer_t timer; +int timer_cb_called; +int close_cb_called; +int fs_event_cb_called; + +static void create_dir(uv_loop_t* loop, const char* name) { + int r; + uv_fs_t req; + r = uv_fs_mkdir(loop, &req, name, 0755, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); +} + +static void create_file(uv_loop_t* loop, const char* name) { + int r; + uv_file file; + uv_fs_t req; + + r = uv_fs_open(loop, &req, name, O_WRONLY | O_CREAT, + S_IWRITE | S_IREAD, NULL); + ASSERT(r != -1); + file = r; + uv_fs_req_cleanup(&req); + r = uv_fs_close(loop, &req, file, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&req); +} + +static void touch_file(uv_loop_t* loop, const char* name) { + int r; + uv_file file; + uv_fs_t req; + + r = uv_fs_open(loop, &req, name, O_RDWR, 0, NULL); + ASSERT(r != -1); + file = r; + uv_fs_req_cleanup(&req); + + r = uv_fs_write(loop, &req, file, "foo", 4, -1, NULL); + ASSERT(r != -1); + uv_fs_req_cleanup(&req); + + r = uv_fs_close(loop, &req, file, NULL); + ASSERT(r != -1); + uv_fs_req_cleanup(&req); +} + +static void close_cb(uv_handle_t* handle) { + ASSERT(handle != NULL); + close_cb_called++; +} + +static void fs_event_cb_dir(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); + ASSERT(strcmp(filename, "file1") == 0); + uv_close((uv_handle_t*)handle, close_cb); +} + +static void fs_event_cb_file(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_CHANGE); + ASSERT(strcmp(filename, "file2") == 0); + uv_close((uv_handle_t*)handle, close_cb); +} + +static void fs_event_cb_file_current_dir(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_CHANGE); + ASSERT(strcmp(filename, "watch_file") == 0); + uv_close((uv_handle_t*)handle, close_cb); +} + +static void timer_cb_dir(uv_timer_t* handle, int status) { + ++timer_cb_called; + create_file(handle->loop, "watch_dir/file1"); + uv_close((uv_handle_t*)handle, close_cb); +} + +static void timer_cb_file(uv_timer_t* handle, int status) { + ++timer_cb_called; + + if (timer_cb_called == 1) { + touch_file(handle->loop, "watch_dir/file1"); + } else { + touch_file(handle->loop, "watch_dir/file2"); + uv_close((uv_handle_t*)handle, close_cb); + } +} + +TEST_IMPL(fs_event_watch_dir) { + uv_fs_t fs_req; + uv_loop_t* loop = uv_default_loop(); + int r; + + /* Setup */ + uv_fs_unlink(loop, &fs_req, "watch_dir/file1", NULL); + uv_fs_unlink(loop, &fs_req, "watch_dir/file2", NULL); + uv_fs_rmdir(loop, &fs_req, "watch_dir", NULL); + create_dir(loop, "watch_dir"); + + r = uv_fs_event_init(loop, &fs_event, "watch_dir", fs_event_cb_dir); + ASSERT(r != -1); + r = uv_timer_init(loop, &timer); + ASSERT(r != -1); + r = uv_timer_start(&timer, timer_cb_dir, 100, 0); + ASSERT(r != -1); + + uv_run(loop); + + ASSERT(fs_event_cb_called == 1); + ASSERT(timer_cb_called == 1); + ASSERT(close_cb_called == 2); + + /* Cleanup */ + r = uv_fs_unlink(loop, &fs_req, "watch_dir/file1", NULL); + r = uv_fs_unlink(loop, &fs_req, "watch_dir/file2", NULL); + r = uv_fs_rmdir(loop, &fs_req, "watch_dir", NULL); + + return 0; +} + +TEST_IMPL(fs_event_watch_file) { + uv_fs_t fs_req; + uv_loop_t* loop = uv_default_loop(); + int r; + + /* Setup */ + uv_fs_unlink(loop, &fs_req, "watch_dir/file1", NULL); + uv_fs_unlink(loop, &fs_req, "watch_dir/file2", NULL); + uv_fs_rmdir(loop, &fs_req, "watch_dir", NULL); + create_dir(loop, "watch_dir"); + create_file(loop, "watch_dir/file1"); + create_file(loop, "watch_dir/file2"); + + r = uv_fs_event_init(loop, &fs_event, "watch_dir/file2", fs_event_cb_file); + ASSERT(r != -1); + r = uv_timer_init(loop, &timer); + ASSERT(r != -1); + r = uv_timer_start(&timer, timer_cb_file, 100, 100); + ASSERT(r != -1); + + uv_run(loop); + + ASSERT(fs_event_cb_called == 1); + ASSERT(timer_cb_called == 2); + ASSERT(close_cb_called == 2); + + /* Cleanup */ + r = uv_fs_unlink(loop, &fs_req, "watch_dir/file1", NULL); + r = uv_fs_unlink(loop, &fs_req, "watch_dir/file2", NULL); + r = uv_fs_rmdir(loop, &fs_req, "watch_dir", NULL); + + return 0; +} + +TEST_IMPL(fs_event_watch_file_current_dir) { + uv_fs_t fs_req; + uv_loop_t* loop = uv_default_loop(); + int r; + + /* Setup */ + uv_fs_unlink(loop, &fs_req, "watch_file", NULL); + create_file(loop, "watch_file"); + + r = uv_fs_event_init(loop, &fs_event, "watch_file", + fs_event_cb_file_current_dir); + ASSERT(r != -1); + + touch_file(loop, "watch_file"); + + uv_run(loop); + + ASSERT(fs_event_cb_called == 1); + ASSERT(close_cb_called == 1); + + /* Cleanup */ + r = uv_fs_unlink(loop, &fs_req, "watch_file", NULL); + return 0; +} diff --git a/test/test-list.h b/test/test-list.h index 429773f275..bf376e3914 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -87,6 +87,9 @@ TEST_DECLARE (fs_link) TEST_DECLARE (fs_symlink) TEST_DECLARE (fs_utime) TEST_DECLARE (fs_futime) +TEST_DECLARE (fs_event_watch_dir) +TEST_DECLARE (fs_event_watch_file) +TEST_DECLARE (fs_event_watch_file_current_dir) TEST_DECLARE (threadpool_queue_work_simple) #ifdef _WIN32 TEST_DECLARE (spawn_detect_pipe_name_collisions_on_windows) @@ -201,6 +204,9 @@ TASK_LIST_START TEST_ENTRY (fs_utime) TEST_ENTRY (fs_futime) TEST_ENTRY (fs_symlink) + TEST_ENTRY (fs_event_watch_dir) + TEST_ENTRY (fs_event_watch_file) + TEST_ENTRY (fs_event_watch_file_current_dir) TEST_ENTRY (threadpool_queue_work_simple) diff --git a/uv.gyp b/uv.gyp index 6600a2f51a..e32dddd3b5 100644 --- a/uv.gyp +++ b/uv.gyp @@ -103,6 +103,7 @@ 'src/win/core.c', 'src/win/error.c', 'src/win/fs.c', + 'src/win/fs-event.c', 'src/win/getaddrinfo.c', 'src/win/handle.c', 'src/win/internal.h', @@ -238,6 +239,7 @@ 'test/test-delayed-accept.c', 'test/test-fail-always.c', 'test/test-fs.c', + 'test/test-fs-event.c', 'test/test-get-currentexe.c', 'test/test-getaddrinfo.c', 'test/test-gethostbyname.c', @@ -334,3 +336,4 @@ ] } +