update dmon to work on macos

This commit is contained in:
2025-11-22 01:00:03 -06:00
parent d61f98f81d
commit 906b60276a
3 changed files with 190 additions and 140 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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
@@ -399,12 +405,11 @@ typedef struct dmon__watch_state {
typedef struct dmon__state {
int num_watches;
dmon__watch_state* watches[DMON_MAX_WATCHES];
int freelist[DMON_MAX_WATCHES];
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)) {
Sleep(DMON_SLEEP_INTERVAL);
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]) {
dmon__watch_state* watch = _dmon.watches[i];
watch_states[i] = watch;
wait_handles[i] = watch->overlapped.hEvent;
DWORD active = 0;
for (unsigned i = 0; i < DMON_MAX_WATCHES; i++) {
dmon__watch_state* watch = _dmon.watches[i];
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);
}
}
@@ -589,8 +592,11 @@ DMON_API_IMPL void dmon_init(void)
_dmon.thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_dmon_thread, NULL, 0, NULL);
DMON_ASSERT(_dmon.thread_handle);
for (int i = 0; i < DMON_MAX_WATCHES; i++)
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,19 +605,16 @@ 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++) {
if (_dmon.watches[i]) {
_dmon_unwatch(_dmon.watches[i]);
DMON_FREE(_dmon.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]);
}
}
@@ -627,40 +630,65 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
const char* oldname, void* user),
uint32_t flags, void* user_data)
{
DMON_ASSERT(_dmon_init);
DMON_ASSERT(_dmon_init);
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);
if (!_dmon_refresh_watch(watch)) {
_dmon_unwatch(watch);
DMON_LOG_ERROR("ReadDirectoryChanges failed");
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)
{
DMON_ASSERT(_dmon_init);
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);
}
LeaveCriticalSection(&_dmon.mutex);
}
#elif DMON_OS_LINUX
@@ -761,7 +786,7 @@ typedef struct dmon__watch_state {
typedef struct dmon__state {
dmon__watch_state* watches[DMON_MAX_WATCHES];
int freelist[DMON_MAX_WATCHES];
int freelist[DMON_MAX_WATCHES];
dmon__inotify_event* events;
int num_watches;
pthread_t thread_handle;
@@ -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,10 +992,11 @@ _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);
{
@@ -1159,7 +1204,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
const char* oldname, void* user),
uint32_t flags, void* user_data)
{
DMON_ASSERT(_dmon_init);
DMON_ASSERT(_dmon_init);
DMON_ASSERT(watch_cb);
DMON_ASSERT(rootdir && rootdir[0]);
@@ -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);
@@ -1260,7 +1305,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
{
DMON_ASSERT(_dmon_init);
DMON_ASSERT(_dmon_init);
DMON_ASSERT(id.id > 0);
int index = id.id - 1;
DMON_ASSERT(index < DMON_MAX_WATCHES);
@@ -1307,10 +1352,9 @@ typedef struct dmon__watch_state {
typedef struct dmon__state {
dmon__watch_state* watches[DMON_MAX_WATCHES];
int freelist[DMON_MAX_WATCHES];
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);
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
FSEventStreamScheduleWithRunLoop(watch->fsev_stream_ref, _dmon.cf_loop_ref, kCFRunLoopDefaultMode);
FSEventStreamStart(watch->fsev_stream_ref);
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
@@ -1605,11 +1642,10 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
const char* oldname, void* user),
uint32_t flags, void* user_data)
{
DMON_ASSERT(_dmon_init);
DMON_ASSERT(_dmon_init);
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,17 +1738,17 @@ 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);
}
DMON_API_IMPL void dmon_unwatch(dmon_watch_id id)
{
DMON_ASSERT(_dmon_init);
DMON_ASSERT(_dmon_init);
DMON_ASSERT(id.id > 0);
int index = id.id - 1;
DMON_ASSERT(index < DMON_MAX_WATCHES);
@@ -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,11 +1767,10 @@ 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);
}
}
#endif
#endif // DMON_IMPL
#endif // __DMON_H__
#endif // __DMON_H__