| /* 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 "internal.h" |
| |
| #if TARGET_OS_IPHONE |
| |
| /* iOS (currently) doesn't provide the FSEvents-API (nor CoreServices) */ |
| |
| int uv__fsevents_init(uv_fs_event_t* handle) { |
| return 0; |
| } |
| |
| |
| int uv__fsevents_close(uv_fs_event_t* handle) { |
| return 0; |
| } |
| |
| #else /* TARGET_OS_IPHONE */ |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <CoreServices/CoreServices.h> |
| |
| typedef struct uv__fsevents_event_s uv__fsevents_event_t; |
| |
| struct uv__fsevents_event_s { |
| int events; |
| ngx_queue_t member; |
| char path[1]; |
| }; |
| |
| |
| #define UV__FSEVENTS_WALK(handle, block) \ |
| { \ |
| ngx_queue_t* curr; \ |
| ngx_queue_t split_head; \ |
| uv__fsevents_event_t* event; \ |
| uv_mutex_lock(&(handle)->cf_mutex); \ |
| ngx_queue_init(&split_head); \ |
| if (!ngx_queue_empty(&(handle)->cf_events)) { \ |
| ngx_queue_t* split_pos = ngx_queue_next(&(handle)->cf_events); \ |
| ngx_queue_split(&(handle)->cf_events, split_pos, &split_head); \ |
| } \ |
| uv_mutex_unlock(&(handle)->cf_mutex); \ |
| while (!ngx_queue_empty(&split_head)) { \ |
| curr = ngx_queue_head(&split_head); \ |
| /* Invoke callback */ \ |
| event = ngx_queue_data(curr, uv__fsevents_event_t, member); \ |
| ngx_queue_remove(curr); \ |
| /* Invoke block code, but only if handle wasn't closed */ \ |
| if (((handle)->flags & (UV_CLOSING | UV_CLOSED)) == 0) \ |
| block \ |
| /* Free allocated data */ \ |
| free(event); \ |
| } \ |
| } |
| |
| |
| void uv__fsevents_cb(uv_async_t* cb, int status) { |
| uv_fs_event_t* handle; |
| |
| handle = cb->data; |
| |
| UV__FSEVENTS_WALK(handle, { |
| if (handle->event_watcher.fd != -1) |
| handle->cb(handle, event->path[0] ? event->path : NULL, event->events, 0); |
| }); |
| |
| if ((handle->flags & (UV_CLOSING | UV_CLOSED)) == 0 && |
| handle->event_watcher.fd == -1) { |
| uv__fsevents_close(handle); |
| } |
| } |
| |
| |
| void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef, |
| void* info, |
| size_t numEvents, |
| void* eventPaths, |
| const FSEventStreamEventFlags eventFlags[], |
| const FSEventStreamEventId eventIds[]) { |
| size_t i; |
| int len; |
| char** paths; |
| char* path; |
| char* pos; |
| uv_fs_event_t* handle; |
| uv__fsevents_event_t* event; |
| ngx_queue_t add_list; |
| int kFSEventsModified; |
| int kFSEventsRenamed; |
| |
| kFSEventsModified = kFSEventStreamEventFlagItemFinderInfoMod | |
| kFSEventStreamEventFlagItemModified | |
| kFSEventStreamEventFlagItemInodeMetaMod | |
| kFSEventStreamEventFlagItemChangeOwner | |
| kFSEventStreamEventFlagItemXattrMod; |
| kFSEventsRenamed = kFSEventStreamEventFlagItemCreated | |
| kFSEventStreamEventFlagItemRemoved | |
| kFSEventStreamEventFlagItemRenamed; |
| |
| handle = info; |
| paths = eventPaths; |
| ngx_queue_init(&add_list); |
| |
| for (i = 0; i < numEvents; i++) { |
| /* Ignore system events */ |
| if (eventFlags[i] & (kFSEventStreamEventFlagUserDropped | |
| kFSEventStreamEventFlagKernelDropped | |
| kFSEventStreamEventFlagEventIdsWrapped | |
| kFSEventStreamEventFlagHistoryDone | |
| kFSEventStreamEventFlagMount | |
| kFSEventStreamEventFlagUnmount | |
| kFSEventStreamEventFlagRootChanged)) { |
| continue; |
| } |
| |
| /* TODO: Report errors */ |
| path = paths[i]; |
| len = strlen(path); |
| |
| /* Remove absolute path prefix */ |
| if (strstr(path, handle->realpath) == path) { |
| path += handle->realpath_len; |
| len -= handle->realpath_len; |
| |
| /* Skip back slash */ |
| if (*path != 0) { |
| path++; |
| len--; |
| } |
| } |
| |
| #ifdef MAC_OS_X_VERSION_10_7 |
| /* Ignore events with path equal to directory itself */ |
| if (len == 0) |
| continue; |
| #endif /* MAC_OS_X_VERSION_10_7 */ |
| |
| /* Do not emit events from subdirectories (without option set) */ |
| pos = strchr(path, '/'); |
| if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && |
| pos != NULL && |
| pos != path + 1) |
| continue; |
| |
| #ifndef MAC_OS_X_VERSION_10_7 |
| path = ""; |
| len = 0; |
| #endif /* MAC_OS_X_VERSION_10_7 */ |
| |
| event = malloc(sizeof(*event) + len); |
| if (event == NULL) |
| break; |
| |
| memcpy(event->path, path, len + 1); |
| |
| if ((eventFlags[i] & kFSEventsModified) != 0 && |
| (eventFlags[i] & kFSEventsRenamed) == 0) |
| event->events = UV_CHANGE; |
| else |
| event->events = UV_RENAME; |
| |
| ngx_queue_insert_tail(&add_list, &event->member); |
| } |
| uv_mutex_lock(&handle->cf_mutex); |
| ngx_queue_add(&handle->cf_events, &add_list); |
| uv_mutex_unlock(&handle->cf_mutex); |
| |
| uv_async_send(handle->cf_cb); |
| } |
| |
| |
| void uv__fsevents_schedule(void* arg) { |
| uv_fs_event_t* handle; |
| |
| handle = arg; |
| FSEventStreamScheduleWithRunLoop(handle->cf_eventstream, |
| handle->loop->cf_loop, |
| kCFRunLoopDefaultMode); |
| FSEventStreamStart(handle->cf_eventstream); |
| uv_sem_post(&handle->cf_sem); |
| } |
| |
| |
| int uv__fsevents_init(uv_fs_event_t* handle) { |
| FSEventStreamContext ctx; |
| FSEventStreamRef ref; |
| CFStringRef path; |
| CFArrayRef paths; |
| CFAbsoluteTime latency; |
| FSEventStreamCreateFlags flags; |
| |
| /* Initialize context */ |
| ctx.version = 0; |
| ctx.info = handle; |
| ctx.retain = NULL; |
| ctx.release = NULL; |
| ctx.copyDescription = NULL; |
| |
| /* Get absolute path to file */ |
| handle->realpath = realpath(handle->filename, NULL); |
| if (handle->realpath != NULL) |
| handle->realpath_len = strlen(handle->realpath); |
| |
| /* Initialize paths array */ |
| path = CFStringCreateWithFileSystemRepresentation(NULL, handle->filename); |
| paths = CFArrayCreate(NULL, (const void**)&path, 1, NULL); |
| |
| latency = 0.15; |
| |
| /* Set appropriate flags */ |
| flags = kFSEventStreamCreateFlagFileEvents; |
| |
| ref = FSEventStreamCreate(NULL, |
| &uv__fsevents_event_cb, |
| &ctx, |
| paths, |
| kFSEventStreamEventIdSinceNow, |
| latency, |
| flags); |
| handle->cf_eventstream = ref; |
| |
| /* |
| * Events will occur in other thread. |
| * Initialize callback for getting them back into event loop's thread |
| */ |
| handle->cf_cb = malloc(sizeof(*handle->cf_cb)); |
| if (handle->cf_cb == NULL) |
| return uv__set_sys_error(handle->loop, ENOMEM); |
| |
| handle->cf_cb->data = handle; |
| uv_async_init(handle->loop, handle->cf_cb, uv__fsevents_cb); |
| handle->cf_cb->flags |= UV__HANDLE_INTERNAL; |
| uv_unref((uv_handle_t*) handle->cf_cb); |
| |
| uv_mutex_init(&handle->cf_mutex); |
| uv_sem_init(&handle->cf_sem, 0); |
| ngx_queue_init(&handle->cf_events); |
| |
| uv__cf_loop_signal(handle->loop, uv__fsevents_schedule, handle); |
| |
| return 0; |
| } |
| |
| |
| int uv__fsevents_close(uv_fs_event_t* handle) { |
| if (handle->cf_eventstream == NULL) |
| return -1; |
| |
| /* Ensure that event stream was scheduled */ |
| uv_sem_wait(&handle->cf_sem); |
| |
| /* Stop emitting events */ |
| FSEventStreamStop(handle->cf_eventstream); |
| |
| /* Release stream */ |
| FSEventStreamInvalidate(handle->cf_eventstream); |
| FSEventStreamRelease(handle->cf_eventstream); |
| handle->cf_eventstream = NULL; |
| |
| uv_close((uv_handle_t*) handle->cf_cb, (uv_close_cb) free); |
| |
| /* Free data in queue */ |
| UV__FSEVENTS_WALK(handle, { |
| /* NOP */ |
| }) |
| |
| uv_mutex_destroy(&handle->cf_mutex); |
| uv_sem_destroy(&handle->cf_sem); |
| free(handle->realpath); |
| handle->realpath = NULL; |
| handle->realpath_len = 0; |
| |
| return 0; |
| } |
| |
| #endif /* TARGET_OS_IPHONE */ |