From a7888e96ea6f3c931c89167d2112ea6e8311b09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jefferson=20Gonz=C3=A1lez?= Date: Mon, 10 Oct 2022 17:40:41 -0700 Subject: [PATCH] Add fsevents backend to dirmonitor (#1141) * dirmonitor: added backend reporting of watch mode * dirmonitor: added fsevents backend for macos --- data/core/dirwatch.lua | 34 ++++--- meson_options.txt | 2 +- src/api/dirmonitor.c | 12 +++ src/api/dirmonitor/dummy.c | 3 +- src/api/dirmonitor/fsevents.c | 181 ++++++++++++++++++++++++++++++++++ src/api/dirmonitor/inotify.c | 5 +- src/api/dirmonitor/kqueue.c | 5 +- src/api/dirmonitor/win32.c | 5 +- src/meson.build | 2 + 9 files changed, 229 insertions(+), 20 deletions(-) create mode 100644 src/api/dirmonitor/fsevents.c diff --git a/data/core/dirwatch.lua b/data/core/dirwatch.lua index 5553047d..059291f1 100644 --- a/data/core/dirwatch.lua +++ b/data/core/dirwatch.lua @@ -2,7 +2,7 @@ local common = require "core.common" local config = require "core.config" local dirwatch = {} -function dirwatch:__index(idx) +function dirwatch:__index(idx) local value = rawget(self, idx) if value ~= nil then return value end return dirwatch[idx] @@ -14,8 +14,8 @@ function dirwatch.new() watched = {}, reverse_watched = {}, monitor = dirmonitor.new(), - windows_watch_top = nil, - windows_watch_count = 0 + single_watch_top = nil, + single_watch_count = 0 } setmetatable(t, dirwatch) return t @@ -38,23 +38,23 @@ function dirwatch:watch(directory, bool) local info = system.get_file_info(directory) if not info then return end if not self.watched[directory] and not self.scanned[directory] then - if PLATFORM == "Windows" then + if self.monitor:mode() == "single" then if info.type ~= "dir" then return self:scan(directory) end - if not self.windows_watch_top or directory:find(self.windows_watch_top, 1, true) ~= 1 then + if not self.single_watch_top or directory:find(self.single_watch_top, 1, true) ~= 1 then -- Get the highest level of directory that is common to this directory, and the original. local target = directory - while self.windows_watch_top and self.windows_watch_top:find(target, 1, true) ~= 1 do + while self.single_watch_top and self.single_watch_top:find(target, 1, true) ~= 1 do target = common.dirname(target) end - if target ~= self.windows_watch_top then + if target ~= self.single_watch_top then local value = self.monitor:watch(target) if value and value < 0 then return self:scan(directory) end - self.windows_watch_top = target + self.single_watch_top = target end end - self.windows_watch_count = self.windows_watch_count + 1 + self.single_watch_count = self.single_watch_count + 1 self.watched[directory] = true else local value = self.monitor:watch(directory) @@ -72,13 +72,13 @@ end -- this should be an absolute path function dirwatch:unwatch(directory) if self.watched[directory] then - if PLATFORM ~= "Windows" then + if self.monitor:mode() == "multiple" then self.monitor:unwatch(self.watched[directory]) self.reverse_watched[directory] = nil else - self.windows_watch_count = self.windows_watch_count - 1 - if self.windows_watch_count == 0 then - self.windows_watch_top = nil + self.single_watch_count = self.single_watch_count - 1 + if self.single_watch_count == 0 then + self.single_watch_top = nil self.monitor:unwatch(directory) end end @@ -93,8 +93,12 @@ function dirwatch:check(change_callback, scan_time, wait_time) local had_change = false self.monitor:check(function(id) had_change = true - if PLATFORM == "Windows" then - change_callback(common.dirname(self.windows_watch_top .. PATHSEP .. id)) + if self.monitor:mode() == "single" then + local path = common.dirname(id) + if not string.match(id, "^/") and not string.match(id, "^%a:[/\\]") then + path = common.dirname(self.single_watch_top .. PATHSEP .. id) + end + change_callback(path) elseif self.reverse_watched[id] then change_callback(self.reverse_watched[id]) end diff --git a/meson_options.txt b/meson_options.txt index 7850416e..5d80efce 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -2,4 +2,4 @@ option('bundle', type : 'boolean', value : false, description: 'Build a macOS bu option('source-only', type : 'boolean', value : false, description: 'Configure source files only, doesn\'t checks for dependencies') option('portable', type : 'boolean', value : false, description: 'Portable install') option('renderer', type : 'boolean', value : false, description: 'Use SDL renderer') -option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use') \ No newline at end of file +option('dirmonitor_backend', type : 'combo', value : '', choices : ['', 'inotify', 'fsevents', 'kqueue', 'win32', 'dummy'], description: 'define what dirmonitor backend to use') diff --git a/src/api/dirmonitor.c b/src/api/dirmonitor.c index 73dfb348..2769aa6b 100644 --- a/src/api/dirmonitor.c +++ b/src/api/dirmonitor.c @@ -23,6 +23,7 @@ int get_changes_dirmonitor(struct dirmonitor_internal*, char*, int); int translate_changes_dirmonitor(struct dirmonitor_internal*, char*, int, int (*)(int, const char*, void*), void*); int add_dirmonitor(struct dirmonitor_internal*, const char*); void remove_dirmonitor(struct dirmonitor_internal*, int); +int get_mode_dirmonitor(); static int f_check_dir_callback(int watch_id, const char* path, void* L) { @@ -111,12 +112,23 @@ static int f_dirmonitor_check(lua_State* L) { } +static int f_dirmonitor_mode(lua_State* L) { + int mode = get_mode_dirmonitor(); + if (mode == 1) + lua_pushstring(L, "single"); + else + lua_pushstring(L, "multiple"); + return 1; +} + + static const luaL_Reg dirmonitor_lib[] = { { "new", f_dirmonitor_new }, { "__gc", f_dirmonitor_gc }, { "watch", f_dirmonitor_watch }, { "unwatch", f_dirmonitor_unwatch }, { "check", f_dirmonitor_check }, + { "mode", f_dirmonitor_mode }, {NULL, NULL} }; diff --git a/src/api/dirmonitor/dummy.c b/src/api/dirmonitor/dummy.c index 62b1e624..47aa0990 100644 --- a/src/api/dirmonitor/dummy.c +++ b/src/api/dirmonitor/dummy.c @@ -5,4 +5,5 @@ void deinit_dirmonitor(struct dirmonitor_internal* monitor) { } int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, size_t len) { return -1; } int translate_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, int size, int (*callback)(int, const char*, void*), void* data) { return -1; } int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { return -1; } -void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { } \ No newline at end of file +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { } +int get_mode_dirmonitor() { return 1; } diff --git a/src/api/dirmonitor/fsevents.c b/src/api/dirmonitor/fsevents.c new file mode 100644 index 00000000..6d22ca51 --- /dev/null +++ b/src/api/dirmonitor/fsevents.c @@ -0,0 +1,181 @@ +#include +#include + +struct dirmonitor_internal { + SDL_mutex* lock; + char** changes; + size_t count; + FSEventStreamRef stream; +}; + +CFRunLoopRef main_run_loop; + + +struct dirmonitor_internal* init_dirmonitor() { + static bool mainloop_registered = false; + if (!mainloop_registered) { + main_run_loop = CFRunLoopGetCurrent(); + mainloop_registered = true; + } + + struct dirmonitor_internal* monitor = malloc(sizeof(struct dirmonitor_internal)); + monitor->stream = NULL; + monitor->changes = NULL; + monitor->count = 0; + + return monitor; +} + + +static void stop_monitor_stream(struct dirmonitor_internal* monitor) { + if (monitor->stream) { + FSEventStreamStop(monitor->stream); + FSEventStreamUnscheduleFromRunLoop( + monitor->stream, main_run_loop, kCFRunLoopDefaultMode + ); + FSEventStreamInvalidate(monitor->stream); + FSEventStreamRelease(monitor->stream); + monitor->stream = NULL; + + SDL_LockMutex(monitor->lock); + if (monitor->count > 0) { + for (size_t i = 0; icount; i++) { + free(monitor->changes[i]); + } + free(monitor->changes); + monitor->changes = NULL; + monitor->count = 0; + } + SDL_UnlockMutex(monitor->lock); + SDL_DestroyMutex(monitor->lock); + } +} + + +void deinit_dirmonitor(struct dirmonitor_internal* monitor) { + stop_monitor_stream(monitor); +} + + +static void stream_callback( + ConstFSEventStreamRef streamRef, + void* monitor_ptr, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[] +) +{ + if (numEvents <= 0) { + return; + } + + struct dirmonitor_internal* monitor = monitor_ptr; + char** path_list = eventPaths; + + SDL_LockMutex(monitor->lock); + size_t total = 0; + if (monitor->count == 0) { + total = numEvents; + monitor->changes = calloc(numEvents, sizeof(char*)); + } else { + total = monitor->count + numEvents; + monitor->changes = realloc( + monitor->changes, + sizeof(char*) * total + ); + } + for (size_t idx = monitor->count; idx < total; idx++) { + size_t pidx = idx - monitor->count; + monitor->changes[idx] = malloc(strlen(path_list[pidx])+1); + strcpy(monitor->changes[idx], path_list[pidx]); + } + monitor->count = total; + SDL_UnlockMutex(monitor->lock); +} + + +int get_changes_dirmonitor( + struct dirmonitor_internal* monitor, + char* buffer, + int buffer_size +) { + FSEventStreamFlushSync(monitor->stream); + + size_t results = 0; + SDL_LockMutex(monitor->lock); + results = monitor->count; + SDL_UnlockMutex(monitor->lock); + + return results; +} + + +int translate_changes_dirmonitor( + struct dirmonitor_internal* monitor, + char* buffer, + int buffer_size, + int (*change_callback)(int, const char*, void*), + void* L +) { + SDL_LockMutex(monitor->lock); + if (monitor->count > 0) { + for (size_t i = 0; icount; i++) { + change_callback(strlen(monitor->changes[i]), monitor->changes[i], L); + free(monitor->changes[i]); + } + free(monitor->changes); + monitor->changes = NULL; + monitor->count = 0; + } + SDL_UnlockMutex(monitor->lock); + return 0; +} + + +int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { + stop_monitor_stream(monitor); + + FSEventStreamContext context = { + .info = monitor, + .retain = NULL, + .release = NULL, + .copyDescription = NULL, + .version = 0 + }; + + CFStringRef paths[] = { + CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8) + }; + + monitor->stream = FSEventStreamCreate( + NULL, + stream_callback, + &context, + CFArrayCreate(NULL, (const void **)&paths, 1, NULL), + kFSEventStreamEventIdSinceNow, + 10000, + kFSEventStreamCreateFlagNone + | kFSEventStreamCreateFlagWatchRoot + | kFSEventStreamCreateFlagFileEvents + ); + + FSEventStreamScheduleWithRunLoop( + monitor->stream, main_run_loop, kCFRunLoopDefaultMode + ); + + if (!FSEventStreamStart(monitor->stream)) { + stop_monitor_stream(monitor); + return -1; + } + + return 1; +} + + +void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { + stop_monitor_stream(monitor); +} + + +int get_mode_dirmonitor() { return 1; } diff --git a/src/api/dirmonitor/inotify.c b/src/api/dirmonitor/inotify.c index 697e1815..4d2f8458 100644 --- a/src/api/dirmonitor/inotify.c +++ b/src/api/dirmonitor/inotify.c @@ -13,7 +13,7 @@ struct dirmonitor_internal { struct dirmonitor_internal* init_dirmonitor() { - struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); + struct dirmonitor_internal* monitor = calloc(1, sizeof(struct dirmonitor_internal)); monitor->fd = inotify_init(); pipe(monitor->sig); fcntl(monitor->sig[0], F_SETFD, FD_CLOEXEC); @@ -51,3 +51,6 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { inotify_rm_watch(monitor->fd, fd); } + + +int get_mode_dirmonitor() { return 2; } diff --git a/src/api/dirmonitor/kqueue.c b/src/api/dirmonitor/kqueue.c index 7c6e89d8..bcdf45dc 100644 --- a/src/api/dirmonitor/kqueue.c +++ b/src/api/dirmonitor/kqueue.c @@ -11,7 +11,7 @@ struct dirmonitor_internal { struct dirmonitor_internal* init_dirmonitor() { - struct dirmonitor_internal* monitor = calloc(sizeof(struct dirmonitor_internal), 1); + struct dirmonitor_internal* monitor = calloc(1, sizeof(struct dirmonitor_internal)); monitor->fd = kqueue(); return monitor; } @@ -53,3 +53,6 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { close(fd); } + + +int get_mode_dirmonitor() { return 2; } diff --git a/src/api/dirmonitor/win32.c b/src/api/dirmonitor/win32.c index 5483584f..1941ebaf 100644 --- a/src/api/dirmonitor/win32.c +++ b/src/api/dirmonitor/win32.c @@ -19,7 +19,7 @@ int get_changes_dirmonitor(struct dirmonitor_internal* monitor, char* buffer, in struct dirmonitor* init_dirmonitor() { - return calloc(sizeof(struct dirmonitor_internal), 1); + return calloc(1, sizeof(struct dirmonitor_internal)); } @@ -60,3 +60,6 @@ int add_dirmonitor(struct dirmonitor_internal* monitor, const char* path) { void remove_dirmonitor(struct dirmonitor_internal* monitor, int fd) { close_monitor_handle(monitor); } + + +int get_mode_dirmonitor() { return 1; } diff --git a/src/meson.build b/src/meson.build index fa4a1390..06bb397c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,8 @@ lite_sources = [ if get_option('dirmonitor_backend') == '' if cc.has_function('inotify_init', prefix : '#include') dirmonitor_backend = 'inotify' + elif host_machine.system() == 'darwin' and cc.check_header('CoreServices/CoreServices.h') + dirmonitor_backend = 'fsevents' elif cc.has_function('kqueue', prefix : '#include') dirmonitor_backend = 'kqueue' elif dependency('libkqueue', required : false).found()