update dmon to work on macos
This commit is contained in:
@@ -205,6 +205,23 @@ function add_item(config)
|
||||
if (Array.isArray(use_config.size)) {
|
||||
use_config.size = {width: use_config.size[0], height: use_config.size[1]};
|
||||
}
|
||||
// Apply max_size constraint
|
||||
if (use_config.max_size) {
|
||||
// For containers with max_size, set explicit size to enable proper clipping
|
||||
if (use_config.contain && use_config.size.width == 0 && use_config.max_size.width != null) {
|
||||
use_config.size.width = use_config.max_size.width;
|
||||
}
|
||||
if (use_config.contain && use_config.size.height == 0 && use_config.max_size.height != null) {
|
||||
use_config.size.height = use_config.max_size.height;
|
||||
}
|
||||
// Clamp any size that exceeds max_size
|
||||
if (use_config.max_size.width != null && use_config.size.width > use_config.max_size.width) {
|
||||
use_config.size.width = use_config.max_size.width;
|
||||
}
|
||||
if (use_config.max_size.height != null && use_config.size.height > use_config.max_size.height) {
|
||||
use_config.size.height = use_config.max_size.height;
|
||||
}
|
||||
}
|
||||
lay_ctx.set_size(item,use_config.size);
|
||||
lay_ctx.set_contain(item,use_config.contain);
|
||||
lay_ctx.set_behave(item,use_config.behave);
|
||||
|
||||
@@ -1062,6 +1062,7 @@ prosperon.create_batch = function create_batch(draw_cmds, done) {
|
||||
////////// dmon hot reload ////////
|
||||
function poll_file_changes() {
|
||||
dmon.poll(e => {
|
||||
log.console(json.encode(e))
|
||||
if (e.action == 'modify' || e.action == 'create') {
|
||||
// Check if it's an image file
|
||||
var ext = e.file.split('.').pop().toLowerCase()
|
||||
@@ -1085,7 +1086,7 @@ function poll_file_changes() {
|
||||
$_.delay(poll_file_changes, 0.5)
|
||||
}
|
||||
|
||||
var dmon = null //use('dmon')
|
||||
var dmon = use('dmon')
|
||||
prosperon.dmon = function()
|
||||
{
|
||||
if (!dmon) return
|
||||
|
||||
262
source/dmon.h
262
source/dmon.h
@@ -67,7 +67,6 @@
|
||||
// - Use FSEventStreamSetDispatchQueue instead of FSEventStreamScheduleWithRunLoop on MacOS
|
||||
// - DMON_WATCHFLAGS_FOLLOW_SYMLINKS does not resolve files
|
||||
// - implement DMON_WATCHFLAGS_OUTOFSCOPE_LINKS
|
||||
// - implement DMON_WATCHFLAGS_IGNORE_DIRECTORIES
|
||||
//
|
||||
// History:
|
||||
// 1.0.0 First version. working Win32/Linux backends
|
||||
@@ -80,6 +79,14 @@
|
||||
// 1.2.2 Name refactoring
|
||||
// 1.3.0 Fixing bugs and proper watch/unwatch handles with freelists. Lower memory consumption, especially on Windows backend
|
||||
// 1.3.1 Fix in MacOS event grouping
|
||||
// 1.3.2 Fixes and improvements for Windows backend
|
||||
// 1.3.3 Fixed thread sanitizer issues with Linux backend
|
||||
// 1.3.4 Fixed thread sanitizer issues with MacOS backend
|
||||
// 1.3.5 Got rid of volatile for quit variable
|
||||
// 1.3.6 Fix deadlock when watch/unwatch API is called from the OnChange callback
|
||||
// 1.3.7 Fix deadlock caused by constantly locking the mutex in the thread loop (recent change)
|
||||
// 1.3.8 Fix a cpp compatiblity compiler bug after recent changes
|
||||
//
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -98,8 +105,7 @@ typedef struct { uint32_t id; } dmon_watch_id;
|
||||
typedef enum dmon_watch_flags_t {
|
||||
DMON_WATCHFLAGS_RECURSIVE = 0x1, // monitor all child directories
|
||||
DMON_WATCHFLAGS_FOLLOW_SYMLINKS = 0x2, // resolve symlinks (linux only)
|
||||
DMON_WATCHFLAGS_OUTOFSCOPE_LINKS = 0x4, // TODO: not implemented yet
|
||||
DMON_WATCHFLAGS_IGNORE_DIRECTORIES = 0x8 // TODO: not implemented yet
|
||||
DMON_WATCHFLAGS_OUTOFSCOPE_LINKS = 0x4 // TODO: not implemented yet
|
||||
} dmon_watch_flags;
|
||||
|
||||
// Action is what operation performed on the file. this value is provided by watch callback
|
||||
@@ -402,9 +408,8 @@ typedef struct dmon__state {
|
||||
int freelist[DMON_MAX_WATCHES];
|
||||
HANDLE thread_handle;
|
||||
CRITICAL_SECTION mutex;
|
||||
volatile LONG modify_watches;
|
||||
dmon__win32_event* events;
|
||||
bool quit;
|
||||
uint32_t quit;
|
||||
} dmon__state;
|
||||
|
||||
static bool _dmon_init;
|
||||
@@ -499,30 +504,30 @@ _DMON_PRIVATE DWORD WINAPI _dmon_thread(LPVOID arg)
|
||||
GetSystemTime(&starttm);
|
||||
uint64_t msecs_elapsed = 0;
|
||||
|
||||
while (!_dmon.quit) {
|
||||
int i;
|
||||
if (_dmon.modify_watches || !TryEnterCriticalSection(&_dmon.mutex)) {
|
||||
while (InterlockedCompareExchange(&_dmon.quit, 0, 0) == 0) {
|
||||
Sleep(DMON_SLEEP_INTERVAL);
|
||||
if (!TryEnterCriticalSection(&_dmon.mutex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_dmon.num_watches == 0) {
|
||||
Sleep(DMON_SLEEP_INTERVAL);
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < DMON_MAX_WATCHES; i++) {
|
||||
if (_dmon.watches[i]) {
|
||||
DWORD active = 0;
|
||||
for (unsigned i = 0; i < DMON_MAX_WATCHES; i++) {
|
||||
dmon__watch_state* watch = _dmon.watches[i];
|
||||
watch_states[i] = watch;
|
||||
wait_handles[i] = watch->overlapped.hEvent;
|
||||
if (watch) {
|
||||
watch_states[active] = watch;
|
||||
wait_handles[active] = watch->overlapped.hEvent;
|
||||
++active;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD wait_result = WaitForMultipleObjects(_dmon.num_watches, wait_handles, FALSE, 10);
|
||||
DWORD wait_result = WaitForMultipleObjects(active, wait_handles, FALSE, 10);
|
||||
DMON_ASSERT(wait_result != WAIT_FAILED);
|
||||
if (wait_result != WAIT_TIMEOUT) {
|
||||
if (wait_result >= WAIT_OBJECT_0 && wait_result < WAIT_OBJECT_0 + active) {
|
||||
dmon__watch_state* watch = watch_states[wait_result - WAIT_OBJECT_0];
|
||||
DMON_ASSERT(HasOverlappedIoCompleted(&watch->overlapped));
|
||||
|
||||
@@ -547,8 +552,6 @@ _DMON_PRIVATE DWORD WINAPI _dmon_thread(LPVOID arg)
|
||||
filepath[count] = TEXT('\0');
|
||||
_dmon_unixpath(filepath, sizeof(filepath), filepath);
|
||||
|
||||
// TODO: ignore directories if flag is set
|
||||
|
||||
if (stb_sb_count(_dmon.events) == 0) {
|
||||
msecs_elapsed = 0;
|
||||
}
|
||||
@@ -559,7 +562,7 @@ _DMON_PRIVATE DWORD WINAPI _dmon_thread(LPVOID arg)
|
||||
offset += notify->NextEntryOffset;
|
||||
} while (notify->NextEntryOffset > 0);
|
||||
|
||||
if (!_dmon.quit) {
|
||||
if (InterlockedCompareExchange(&_dmon.quit, 0, 0) == 0) {
|
||||
_dmon_refresh_watch(watch);
|
||||
}
|
||||
}
|
||||
@@ -590,7 +593,10 @@ DMON_API_IMPL void dmon_init(void)
|
||||
DMON_ASSERT(_dmon.thread_handle);
|
||||
|
||||
for (int i = 0; i < DMON_MAX_WATCHES; i++)
|
||||
{
|
||||
_dmon.freelist[i] = DMON_MAX_WATCHES - i - 1;
|
||||
_dmon.watches[i] = NULL;
|
||||
}
|
||||
|
||||
_dmon_init = true;
|
||||
}
|
||||
@@ -599,21 +605,18 @@ DMON_API_IMPL void dmon_init(void)
|
||||
DMON_API_IMPL void dmon_deinit(void)
|
||||
{
|
||||
DMON_ASSERT(_dmon_init);
|
||||
_dmon.quit = true;
|
||||
InterlockedExchange(&_dmon.quit, 1);
|
||||
if (_dmon.thread_handle != INVALID_HANDLE_VALUE) {
|
||||
WaitForSingleObject(_dmon.thread_handle, INFINITE);
|
||||
CloseHandle(_dmon.thread_handle);
|
||||
}
|
||||
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < DMON_MAX_WATCHES; i++) {
|
||||
for (unsigned i = 0; i < DMON_MAX_WATCHES; i++) {
|
||||
if (_dmon.watches[i]) {
|
||||
_dmon_unwatch(_dmon.watches[i]);
|
||||
DMON_FREE(_dmon.watches[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&_dmon.mutex);
|
||||
stb_sb_free(_dmon.events);
|
||||
@@ -631,36 +634,61 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||
DMON_ASSERT(watch_cb);
|
||||
DMON_ASSERT(rootdir && rootdir[0]);
|
||||
|
||||
_InterlockedExchange(&_dmon.modify_watches, 1);
|
||||
EnterCriticalSection(&_dmon.mutex);
|
||||
|
||||
DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES);
|
||||
if (_dmon.num_watches >= DMON_MAX_WATCHES) {
|
||||
DMON_LOG_ERROR("Exceeding maximum number of watches");
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(0);
|
||||
}
|
||||
|
||||
dmon__watch_state* watch = NULL;
|
||||
unsigned id = 0;
|
||||
HANDLE hEvent = INVALID_HANDLE_VALUE;
|
||||
HANDLE dir_handle = INVALID_HANDLE_VALUE;
|
||||
int num_freelist = DMON_MAX_WATCHES - _dmon.num_watches;
|
||||
int index = _dmon.freelist[num_freelist - 1];
|
||||
uint32_t id = (uint32_t)(index + 1);
|
||||
size_t rootdir_len;
|
||||
|
||||
if (_dmon.watches[index] == NULL) {
|
||||
dmon__watch_state* state = (dmon__watch_state*)DMON_MALLOC(sizeof(dmon__watch_state));
|
||||
DMON_ASSERT(state);
|
||||
if (state == NULL) {
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(0);
|
||||
}
|
||||
memset(state, 0x0, sizeof(dmon__watch_state));
|
||||
_dmon.watches[index] = state;
|
||||
{
|
||||
_DMON_WINAPI_STR(rootdir, DMON_MAX_PATH);
|
||||
dir_handle = CreateFile(_rootdir, GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
||||
NULL);
|
||||
}
|
||||
|
||||
++_dmon.num_watches;
|
||||
if(dir_handle == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
_DMON_LOG_ERRORF("Could not open: %s", rootdir);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dmon__watch_state* watch = _dmon.watches[index];
|
||||
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if(hEvent == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DMON_LOG_ERROR("CreateEvent() failed");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
watch = (dmon__watch_state*)DMON_MALLOC(sizeof(dmon__watch_state));
|
||||
if (!watch)
|
||||
{
|
||||
DMON_LOG_ERROR("Out of memory");
|
||||
goto fail;
|
||||
}
|
||||
memset(watch, 0x0, sizeof(dmon__watch_state));
|
||||
|
||||
id = (uint32_t)(index + 1);
|
||||
|
||||
watch->notify_filter = FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_LAST_WRITE |
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_SIZE;
|
||||
watch->overlapped.hEvent = hEvent;
|
||||
watch->dir_handle = dir_handle;
|
||||
watch->id = _dmon_make_id(id);
|
||||
watch->watch_flags = flags;
|
||||
watch->watch_cb = watch_cb;
|
||||
@@ -668,66 +696,63 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||
|
||||
_dmon_strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir);
|
||||
_dmon_unixpath(watch->rootdir, sizeof(watch->rootdir), rootdir);
|
||||
size_t rootdir_len = strlen(watch->rootdir);
|
||||
rootdir_len = strlen(watch->rootdir);
|
||||
if (watch->rootdir[rootdir_len - 1] != '/') {
|
||||
watch->rootdir[rootdir_len] = '/';
|
||||
watch->rootdir[rootdir_len + 1] = '\0';
|
||||
}
|
||||
|
||||
_DMON_WINAPI_STR(rootdir, DMON_MAX_PATH);
|
||||
watch->dir_handle =
|
||||
CreateFile(_rootdir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
||||
if (watch->dir_handle != INVALID_HANDLE_VALUE) {
|
||||
watch->notify_filter = FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_LAST_WRITE |
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_SIZE;
|
||||
watch->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
DMON_ASSERT(watch->overlapped.hEvent != INVALID_HANDLE_VALUE);
|
||||
|
||||
if (!_dmon_refresh_watch(watch)) {
|
||||
_dmon_unwatch(watch);
|
||||
DMON_LOG_ERROR("ReadDirectoryChanges failed");
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(0);
|
||||
}
|
||||
} else {
|
||||
_DMON_LOG_ERRORF("Could not open: %s", rootdir);
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(0);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
++_dmon.num_watches;
|
||||
|
||||
finish:
|
||||
_dmon.watches[index] = watch;
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(id);
|
||||
|
||||
fail:
|
||||
if(hEvent != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hEvent);
|
||||
if(dir_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(dir_handle);
|
||||
if(watch)
|
||||
{
|
||||
DMON_FREE(watch);
|
||||
watch = NULL;
|
||||
};
|
||||
id = 0;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
|
||||
{
|
||||
EnterCriticalSection(&_dmon.mutex);
|
||||
|
||||
DMON_ASSERT(_dmon_init);
|
||||
DMON_ASSERT(id.id > 0);
|
||||
int index = id.id - 1;
|
||||
DMON_ASSERT(index < DMON_MAX_WATCHES);
|
||||
DMON_ASSERT(_dmon.watches[index]);
|
||||
DMON_ASSERT(_dmon.num_watches > 0);
|
||||
|
||||
if (_dmon.watches[index]) {
|
||||
_InterlockedExchange(&_dmon.modify_watches, 1);
|
||||
EnterCriticalSection(&_dmon.mutex);
|
||||
dmon__watch_state* watch = _dmon.watches[index];
|
||||
DMON_ASSERT(watch);
|
||||
|
||||
_dmon_unwatch(_dmon.watches[index]);
|
||||
DMON_FREE(_dmon.watches[index]);
|
||||
if (watch) {
|
||||
_dmon_unwatch(watch);
|
||||
DMON_FREE(watch);
|
||||
_dmon.watches[index] = NULL;
|
||||
|
||||
--_dmon.num_watches;
|
||||
int num_freelist = DMON_MAX_WATCHES - _dmon.num_watches;
|
||||
_dmon.freelist[num_freelist - 1] = index;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&_dmon.mutex);
|
||||
_InterlockedExchange(&_dmon.modify_watches, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#elif DMON_OS_LINUX
|
||||
@@ -891,7 +916,9 @@ _DMON_PRIVATE void _dmon_inotify_process_events(void)
|
||||
ev->skip = true;
|
||||
break;
|
||||
} else if ((ev->mask & IN_ISDIR) && (check_ev->mask & (IN_ISDIR|IN_MODIFY))) {
|
||||
// handle trailing slashes
|
||||
// in some cases, particularly when created files under sub directories
|
||||
// there can be two modify events for a single subdir one with trailing slash and one without
|
||||
// remove trailing slash from both cases and test
|
||||
int l1 = (int)strlen(ev->filepath);
|
||||
int l2 = (int)strlen(check_ev->filepath);
|
||||
if (ev->filepath[l1-1] == '/') ev->filepath[l1-1] = '\0';
|
||||
@@ -908,12 +935,14 @@ _DMON_PRIVATE void _dmon_inotify_process_events(void)
|
||||
for (j = i + 1; j < c && !loop_break; j++) {
|
||||
dmon__inotify_event* check_ev = &_dmon.events[j];
|
||||
if ((check_ev->mask & IN_MOVED_FROM) && strcmp(ev->filepath, check_ev->filepath) == 0) {
|
||||
// check for rename sequences
|
||||
// there is a case where some programs (like gedit):
|
||||
// when we save, it creates a temp file, and moves it to the file being modified
|
||||
// search for these cases and remove all of them
|
||||
int k;
|
||||
for (k = j + 1; k < c; k++) {
|
||||
dmon__inotify_event* third_ev = &_dmon.events[k];
|
||||
if (third_ev->mask & IN_MOVED_TO && check_ev->cookie == third_ev->cookie) {
|
||||
third_ev->mask = IN_MODIFY; // treat as a modify
|
||||
third_ev->mask = IN_MODIFY; // change to modified
|
||||
ev->skip = check_ev->skip = true;
|
||||
loop_break = true;
|
||||
break;
|
||||
@@ -921,6 +950,7 @@ _DMON_PRIVATE void _dmon_inotify_process_events(void)
|
||||
}
|
||||
} else if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) {
|
||||
// Another case is that file is copied. CREATE and MODIFY happens sequentially
|
||||
// so we ignore MODIFY event
|
||||
check_ev->skip = true;
|
||||
}
|
||||
}
|
||||
@@ -934,7 +964,10 @@ _DMON_PRIVATE void _dmon_inotify_process_events(void)
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If destination is not valid, treat as delete
|
||||
|
||||
// in some environments like nautilus file explorer:
|
||||
// when a file is deleted, it is moved to recycle bin
|
||||
// so if the destination of the move is not valid, it's probably DELETE
|
||||
if (!move_valid) {
|
||||
ev->mask = IN_DELETE;
|
||||
}
|
||||
@@ -948,7 +981,10 @@ _DMON_PRIVATE void _dmon_inotify_process_events(void)
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If source is not valid, treat as create
|
||||
|
||||
// in some environments like nautilus file explorer:
|
||||
// when a file is deleted, it is moved to recycle bin, on undo it is moved back it
|
||||
// so if the destination of the move is not valid, it's probably CREATE
|
||||
if (!move_valid) {
|
||||
ev->mask = IN_CREATE;
|
||||
}
|
||||
@@ -956,6 +992,7 @@ _DMON_PRIVATE void _dmon_inotify_process_events(void)
|
||||
int j;
|
||||
for (j = i + 1; j < c; j++) {
|
||||
dmon__inotify_event* check_ev = &_dmon.events[j];
|
||||
// if the file is DELETED and then MODIFIED after, just ignore the modify event
|
||||
if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) {
|
||||
check_ev->skip = true;
|
||||
break;
|
||||
@@ -997,9 +1034,10 @@ _DMON_PRIVATE void _dmon_inotify_process_events(void)
|
||||
stb_sb_push(watch->subdirs, subdir);
|
||||
stb_sb_push(watch->wds, wd);
|
||||
|
||||
// gather newly created subdirs
|
||||
// some directories may be already created, for instance, with the command: mkdir -p
|
||||
// so we will enumerate them manually and add them to the events
|
||||
_dmon_gather_recursive(watch, watchdir);
|
||||
ev = &_dmon.events[i];
|
||||
ev = &_dmon.events[i]; // gotta refresh the pointer because it may be relocated
|
||||
}
|
||||
}
|
||||
watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
||||
@@ -1039,9 +1077,14 @@ static void* _dmon_thread(void* arg)
|
||||
struct timeval starttm;
|
||||
gettimeofday(&starttm, 0);
|
||||
|
||||
while (!_dmon.quit) {
|
||||
while (__sync_bool_compare_and_swap(&_dmon.quit, false, false)) {
|
||||
nanosleep(&req, &rem);
|
||||
if (_dmon.num_watches == 0 || pthread_mutex_trylock(&_dmon.mutex) != 0) {
|
||||
if (pthread_mutex_trylock(&_dmon.mutex) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_dmon.num_watches == 0) {
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1078,8 +1121,6 @@ static void* _dmon_thread(void* arg)
|
||||
_dmon_strcpy(filepath, sizeof(filepath), subdir);
|
||||
_dmon_strcat(filepath, sizeof(filepath), iev->name);
|
||||
|
||||
// TODO: ignore directories if flag is set
|
||||
|
||||
if (stb_sb_count(_dmon.events) == 0) {
|
||||
usecs_elapsed = 0;
|
||||
}
|
||||
@@ -1119,7 +1160,11 @@ _DMON_PRIVATE void _dmon_unwatch(dmon__watch_state* watch)
|
||||
DMON_API_IMPL void dmon_init(void)
|
||||
{
|
||||
DMON_ASSERT(!_dmon_init);
|
||||
pthread_mutex_init(&_dmon.mutex, NULL);
|
||||
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&_dmon.mutex, &attr);
|
||||
|
||||
int r = pthread_create(&_dmon.thread_handle, NULL, _dmon_thread, NULL);
|
||||
_DMON_UNUSED(r);
|
||||
@@ -1134,7 +1179,7 @@ DMON_API_IMPL void dmon_init(void)
|
||||
DMON_API_IMPL void dmon_deinit(void)
|
||||
{
|
||||
DMON_ASSERT(_dmon_init);
|
||||
_dmon.quit = true;
|
||||
_DMON_UNUSED(__sync_lock_test_and_set(&_dmon.quit, true));
|
||||
pthread_join(_dmon.thread_handle, NULL);
|
||||
|
||||
{
|
||||
@@ -1243,7 +1288,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||
return _dmon_make_id(0);
|
||||
}
|
||||
dmon__watch_subdir subdir;
|
||||
_dmon_strcpy(subdir.rootdir, sizeof(subdir.rootdir), ""); // root dir is a dummy entry
|
||||
_dmon_strcpy(subdir.rootdir, sizeof(subdir.rootdir), ""); // root dir is just a dummy entry
|
||||
stb_sb_push(watch->subdirs, subdir);
|
||||
stb_sb_push(watch->wds, wd);
|
||||
|
||||
@@ -1310,7 +1355,6 @@ typedef struct dmon__state {
|
||||
int freelist[DMON_MAX_WATCHES];
|
||||
dmon__fsevent_event* events;
|
||||
int num_watches;
|
||||
volatile int modify_watches;
|
||||
pthread_t thread_handle;
|
||||
dispatch_semaphore_t thread_sem;
|
||||
pthread_mutex_t mutex;
|
||||
@@ -1356,7 +1400,7 @@ _DMON_PRIVATE void _dmon_fsevent_process_events(void)
|
||||
continue;
|
||||
}
|
||||
|
||||
// remove redundant modifies on a single file
|
||||
// remove redundant modify events on a single file
|
||||
if (ev->event_flags & kFSEventStreamEventFlagItemModified) {
|
||||
int j;
|
||||
for (j = i + 1; j < c; j++) {
|
||||
@@ -1367,8 +1411,7 @@ _DMON_PRIVATE void _dmon_fsevent_process_events(void)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((ev->event_flags & kFSEventStreamEventFlagItemRenamed) && !ev->move_valid) {
|
||||
} else if ((ev->event_flags & kFSEventStreamEventFlagItemRenamed) && !ev->move_valid) {
|
||||
int j;
|
||||
for (j = i + 1; j < c; j++) {
|
||||
dmon__fsevent_event* check_ev = &_dmon.events[j];
|
||||
@@ -1379,7 +1422,10 @@ _DMON_PRIVATE void _dmon_fsevent_process_events(void)
|
||||
}
|
||||
}
|
||||
|
||||
// if not valid rename, treat as remove or create
|
||||
// in some environments like finder file explorer:
|
||||
// when a file is deleted, it is moved to recycle bin
|
||||
// so if the destination of the move is not valid, it's probably DELETE or CREATE
|
||||
// decide CREATE if file exists
|
||||
if (!ev->move_valid) {
|
||||
ev->event_flags &= ~kFSEventStreamEventFlagItemRenamed;
|
||||
|
||||
@@ -1414,10 +1460,10 @@ _DMON_PRIVATE void _dmon_fsevent_process_events(void)
|
||||
watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir_unmod, ev->filepath, NULL,
|
||||
watch->user_data);
|
||||
}
|
||||
|
||||
if (ev->event_flags & kFSEventStreamEventFlagItemModified) {
|
||||
watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir_unmod, ev->filepath, NULL, watch->user_data);
|
||||
}
|
||||
else if (ev->event_flags & kFSEventStreamEventFlagItemRenamed) {
|
||||
} else if (ev->event_flags & kFSEventStreamEventFlagItemRenamed) {
|
||||
int j;
|
||||
for (j = i + 1; j < c; j++) {
|
||||
dmon__fsevent_event* check_ev = &_dmon.events[j];
|
||||
@@ -1427,8 +1473,7 @@ _DMON_PRIVATE void _dmon_fsevent_process_events(void)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ev->event_flags & kFSEventStreamEventFlagItemRemoved) {
|
||||
} else if (ev->event_flags & kFSEventStreamEventFlagItemRemoved) {
|
||||
watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir_unmod, ev->filepath, NULL,
|
||||
watch->user_data);
|
||||
}
|
||||
@@ -1447,15 +1492,14 @@ _DMON_PRIVATE void* _dmon_thread(void* arg)
|
||||
_dmon.cf_loop_ref = CFRunLoopGetCurrent();
|
||||
dispatch_semaphore_signal(_dmon.thread_sem);
|
||||
|
||||
while (!_dmon.quit) {
|
||||
while (__sync_bool_compare_and_swap(&_dmon.quit, false, false)) {
|
||||
int i;
|
||||
if (_dmon.modify_watches || pthread_mutex_trylock(&_dmon.mutex) != 0) {
|
||||
nanosleep(&req, &rem);
|
||||
if (pthread_mutex_trylock(&_dmon.mutex) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_dmon.num_watches == 0) {
|
||||
nanosleep(&req, &rem);
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
continue;
|
||||
}
|
||||
@@ -1464,19 +1508,8 @@ _DMON_PRIVATE void* _dmon_thread(void* arg)
|
||||
dmon__watch_state* watch = _dmon.watches[i];
|
||||
if (!watch->init) {
|
||||
DMON_ASSERT(watch->fsev_stream_ref);
|
||||
// Modified block: Use dispatch queue if macOS >= 13, else run loop
|
||||
#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 130000
|
||||
{
|
||||
dispatch_queue_t queue = dispatch_queue_create("com.dmon.fsevents", DISPATCH_QUEUE_SERIAL);
|
||||
FSEventStreamSetDispatchQueue(watch->fsev_stream_ref, queue);
|
||||
FSEventStreamStart(watch->fsev_stream_ref);
|
||||
}
|
||||
#else
|
||||
{
|
||||
FSEventStreamScheduleWithRunLoop(watch->fsev_stream_ref, _dmon.cf_loop_ref, kCFRunLoopDefaultMode);
|
||||
FSEventStreamStart(watch->fsev_stream_ref);
|
||||
}
|
||||
#endif
|
||||
|
||||
watch->init = true;
|
||||
}
|
||||
@@ -1506,7 +1539,11 @@ _DMON_PRIVATE void _dmon_unwatch(dmon__watch_state* watch)
|
||||
DMON_API_IMPL void dmon_init(void)
|
||||
{
|
||||
DMON_ASSERT(!_dmon_init);
|
||||
pthread_mutex_init(&_dmon.mutex, NULL);
|
||||
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&_dmon.mutex, &attr);
|
||||
|
||||
CFAllocatorContext cf_alloc_ctx = { 0 };
|
||||
cf_alloc_ctx.allocate = _dmon_cf_malloc;
|
||||
@@ -1533,7 +1570,7 @@ DMON_API_IMPL void dmon_init(void)
|
||||
DMON_API_IMPL void dmon_deinit(void)
|
||||
{
|
||||
DMON_ASSERT(_dmon_init);
|
||||
_dmon.quit = true;
|
||||
_DMON_UNUSED(__sync_lock_test_and_set(&_dmon.quit, true));
|
||||
pthread_join(_dmon.thread_handle, NULL);
|
||||
|
||||
dispatch_release(_dmon.thread_sem);
|
||||
@@ -1584,8 +1621,8 @@ _DMON_PRIVATE void _dmon_fsevent_callback(ConstFSEventStreamRef stream_ref, void
|
||||
_dmon_strcpy(abs_filepath, sizeof(abs_filepath), filepath);
|
||||
_dmon_unixpath(abs_filepath, sizeof(abs_filepath), abs_filepath);
|
||||
|
||||
// normalize path for case-insensitive volumes
|
||||
_dmon_tolower(abs_filepath_lower, sizeof(abs_filepath_lower), abs_filepath);
|
||||
// normalize path, so it would be the same on both MacOS file-system types (case/nocase)
|
||||
_dmon_tolower(abs_filepath_lower, sizeof(abs_filepath), abs_filepath);
|
||||
DMON_ASSERT(strstr(abs_filepath_lower, watch->rootdir) == abs_filepath_lower);
|
||||
|
||||
// strip the root dir from the beginning
|
||||
@@ -1609,7 +1646,6 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||
DMON_ASSERT(watch_cb);
|
||||
DMON_ASSERT(rootdir && rootdir[0]);
|
||||
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 1);
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
|
||||
DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES);
|
||||
@@ -1649,7 +1685,6 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||
(root_st.st_mode & S_IRUSR) != S_IRUSR) {
|
||||
_DMON_LOG_ERRORF("Could not open/read directory: %s", rootdir);
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(0);
|
||||
}
|
||||
|
||||
@@ -1664,7 +1699,6 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||
} else {
|
||||
_DMON_LOG_ERRORF("symlinks are unsupported: %s. use DMON_WATCHFLAGS_FOLLOW_SYMLINKS", rootdir);
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(0);
|
||||
}
|
||||
} else {
|
||||
@@ -1704,11 +1738,11 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||
cf_dirarr, kFSEventStreamEventIdSinceNow, 0.25,
|
||||
kFSEventStreamCreateFlagFileEvents);
|
||||
|
||||
|
||||
CFRelease(cf_dirarr);
|
||||
CFRelease(cf_dir);
|
||||
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 0);
|
||||
return _dmon_make_id(id);
|
||||
}
|
||||
|
||||
@@ -1722,7 +1756,6 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
|
||||
DMON_ASSERT(_dmon.num_watches > 0);
|
||||
|
||||
if (_dmon.watches[index]) {
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 1);
|
||||
pthread_mutex_lock(&_dmon.mutex);
|
||||
|
||||
_dmon_unwatch(_dmon.watches[index]);
|
||||
@@ -1734,7 +1767,6 @@ DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
|
||||
_dmon.freelist[num_freelist - 1] = index;
|
||||
|
||||
pthread_mutex_unlock(&_dmon.mutex);
|
||||
__sync_lock_test_and_set(&_dmon.modify_watches, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user