diff options
| author | Francesco Abbate <francesco.bbt@gmail.com> | 2021-10-08 22:10:17 +0200 |
|---|---|---|
| committer | Francesco Abbate <francesco.bbt@gmail.com> | 2021-10-08 22:10:17 +0200 |
| commit | a9f6f01ed03594ff25b8af99f094785b35b4065d (patch) | |
| tree | cfa939f8cd12a2ac6d2421277bb442a8db7b11d2 /src | |
| parent | bba42adc73bf592b9de4aa4a5d44410618ec59ae (diff) | |
| download | lite-xl-a9f6f01ed03594ff25b8af99f094785b35b4065d.tar.gz lite-xl-a9f6f01ed03594ff25b8af99f094785b35b4065d.zip | |
Move dmon files into lib/dmon
Diffstat (limited to 'src')
| -rw-r--r-- | src/dmon.h | 1588 | ||||
| -rw-r--r-- | src/dmon_extra.h | 162 | ||||
| -rw-r--r-- | src/meson.build | 4 |
3 files changed, 2 insertions, 1752 deletions
diff --git a/src/dmon.h b/src/dmon.h deleted file mode 100644 index ed10d11f..00000000 --- a/src/dmon.h +++ /dev/null @@ -1,1588 +0,0 @@ -#ifndef __DMON_H__ -#define __DMON_H__ - -// -// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved. -// License: https://github.com/septag/dmon#license-bsd-2-clause -// -// Portable directory monitoring library -// watches directories for file or directory changes. -// -// Usage: -// define DMON_IMPL and include this file to use it: -// #define DMON_IMPL -// #include "dmon.h" -// -// dmon_init(): -// Call this once at the start of your program. -// This will start a low-priority monitoring thread -// dmon_deinit(): -// Call this when your work with dmon is finished, usually on program terminate -// This will free resources and stop the monitoring thread -// dmon_watch: -// Watch for directories -// You can watch multiple directories by calling this function multiple times -// rootdir: root directory to monitor -// watch_cb: callback function to receive events. -// NOTE that this function is called from another thread, so you should -// beware of data races in your application when accessing data within this -// callback -// flags: watch flags, see dmon_watch_flags_t -// user_data: user pointer that is passed to callback function -// Returns the Id of the watched directory after successful call, or returns Id=0 if error -// dmon_unwatch: -// Remove the directory from watch list -// -// see test.c for the basic example -// -// Configuration: -// You can customize some low-level functionality like malloc and logging by overriding macros: -// -// DMON_MALLOC, DMON_FREE, DMON_REALLOC: -// define these macros to override memory allocations -// default is 'malloc', 'free' and 'realloc' -// DMON_ASSERT: -// define this to provide your own assert -// default is 'assert' -// DMON_LOG_ERROR: -// define this to provide your own logging mechanism -// default implementation logs to stdout and breaks the program -// DMON_LOG_DEBUG -// define this to provide your own extra debug logging mechanism -// default implementation logs to stdout in DEBUG and does nothing in other builds -// DMON_API_DECL, DMON_API_IMPL -// define these to provide your own API declerations. (for example: static) -// default is nothing (which is extern in C language ) -// DMON_MAX_PATH -// Maximum size of path characters -// default is 260 characters -// DMON_MAX_WATCHES -// Maximum number of watch directories -// default is 64 -// -// TODO: -// - 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 -// 1.1.0 MacOS backend -// 1.1.1 Minor fixes, eliminate gcc/clang warnings with -Wall -// 1.1.2 Eliminate some win32 dead code -// 1.1.3 Fixed select not resetting causing high cpu usage on linux -// 1.2.1 inotify (linux) fixes and improvements, added extra functionality header for linux -// to manually add/remove directories manually to the watch handle, in case of large file sets -// - -#include <stdbool.h> -#include <stdint.h> - -#ifndef DMON_API_DECL -# define DMON_API_DECL -#endif - -#ifndef DMON_API_IMPL -# define DMON_API_IMPL -#endif - -typedef struct { uint32_t id; } dmon_watch_id; - -// Pass these flags to `dmon_watch` -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_watch_flags; - -// Action is what operation performed on the file. this value is provided by watch callback -typedef enum dmon_action_t { - DMON_ACTION_CREATE = 1, - DMON_ACTION_DELETE, - DMON_ACTION_MODIFY, - DMON_ACTION_MOVE -} dmon_action; - -#ifdef __cplusplus -extern "C" { -#endif - -DMON_API_DECL void dmon_init(void); -DMON_API_DECL void dmon_deinit(void); - -DMON_API_DECL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* rootdir, const char* filepath, - const char* oldfilepath, void* user), - uint32_t flags, void* user_data); -DMON_API_DECL void dmon_unwatch(dmon_watch_id id); - -#ifdef __cplusplus -} -#endif - -#ifdef DMON_IMPL - -#define DMON_OS_WINDOWS 0 -#define DMON_OS_MACOS 0 -#define DMON_OS_LINUX 0 - -#if defined(_WIN32) || defined(_WIN64) -# undef DMON_OS_WINDOWS -# define DMON_OS_WINDOWS 1 -#elif defined(__linux__) -# undef DMON_OS_LINUX -# define DMON_OS_LINUX 1 -#elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) -# undef DMON_OS_MACOS -# define DMON_OS_MACOS __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ -#else -# define DMON_OS 0 -# error "unsupported platform" -#endif - -#if DMON_OS_WINDOWS -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# ifndef NOMINMAX -# define NOMINMAX -# endif -# include <windows.h> -# include <intrin.h> -# ifdef _MSC_VER -# pragma intrinsic(_InterlockedExchange) -# endif -#elif DMON_OS_LINUX -# ifndef __USE_MISC -# define __USE_MISC -# endif -# include <dirent.h> -# include <errno.h> -# include <fcntl.h> -# include <linux/limits.h> -# include <pthread.h> -# include <sys/inotify.h> -# include <sys/stat.h> -# include <sys/time.h> -# include <time.h> -# include <unistd.h> -# include <stdlib.h> -#elif DMON_OS_MACOS -# include <pthread.h> -# include <CoreServices/CoreServices.h> -# include <sys/time.h> -# include <sys/stat.h> -# include <dispatch/dispatch.h> -#endif - -#ifndef DMON_MALLOC -# include <stdlib.h> -# define DMON_MALLOC(size) malloc(size) -# define DMON_FREE(ptr) free(ptr) -# define DMON_REALLOC(ptr, size) realloc(ptr, size) -#endif - -#ifndef DMON_ASSERT -# include <assert.h> -# define DMON_ASSERT(e) assert(e) -#endif - -#ifndef DMON_LOG_ERROR -# include <stdio.h> -# define DMON_LOG_ERROR(s) do { puts(s); DMON_ASSERT(0); } while(0) -#endif - -#ifndef DMON_LOG_DEBUG -# ifndef NDEBUG -# include <stdio.h> -# define DMON_LOG_DEBUG(s) do { puts(s); } while(0) -# else -# define DMON_LOG_DEBUG(s) -# endif -#endif - -#ifndef DMON_MAX_WATCHES -# define DMON_MAX_WATCHES 64 -#endif - -#ifndef DMON_MAX_PATH -# define DMON_MAX_PATH 260 -#endif - -#define _DMON_UNUSED(x) (void)(x) - -#ifndef _DMON_PRIVATE -# if defined(__GNUC__) || defined(__clang__) -# define _DMON_PRIVATE __attribute__((unused)) static -# else -# define _DMON_PRIVATE static -# endif -#endif - -#include <string.h> - -#ifndef _DMON_LOG_ERRORF -# define _DMON_LOG_ERRORF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_ERROR(msg); } while(0); -#endif - -#ifndef _DMON_LOG_DEBUGF -# define _DMON_LOG_DEBUGF(str, ...) do { char msg[512]; snprintf(msg, sizeof(msg), str, __VA_ARGS__); DMON_LOG_DEBUG(msg); } while(0); -#endif - -#ifndef dmon__min -# define dmon__min(a, b) ((a) < (b) ? (a) : (b)) -#endif - -#ifndef dmon__max -# define dmon__max(a, b) ((a) > (b) ? (a) : (b)) -#endif - -#ifndef dmon__swap -# define dmon__swap(a, b, _type) \ - do { \ - _type tmp = a; \ - a = b; \ - b = tmp; \ - } while (0) -#endif - -#ifndef dmon__make_id -# ifdef __cplusplus -# define dmon__make_id(id) {id} -# else -# define dmon__make_id(id) (dmon_watch_id) {id} -# endif -#endif // dmon__make_id - -_DMON_PRIVATE bool dmon__isrange(char ch, char from, char to) -{ - return (uint8_t)(ch - from) <= (uint8_t)(to - from); -} - -_DMON_PRIVATE bool dmon__isupperchar(char ch) -{ - return dmon__isrange(ch, 'A', 'Z'); -} - -_DMON_PRIVATE char dmon__tolowerchar(char ch) -{ - return ch + (dmon__isupperchar(ch) ? 0x20 : 0); -} - -_DMON_PRIVATE char* dmon__tolower(char* dst, int dst_sz, const char* str) -{ - int offset = 0; - int dst_max = dst_sz - 1; - while (*str && offset < dst_max) { - dst[offset++] = dmon__tolowerchar(*str); - ++str; - } - dst[offset] = '\0'; - return dst; -} - -_DMON_PRIVATE char* dmon__strcpy(char* dst, int dst_sz, const char* src) -{ - DMON_ASSERT(dst); - DMON_ASSERT(src); - - const int32_t len = (int32_t)strlen(src); - const int32_t _max = dst_sz - 1; - const int32_t num = (len < _max ? len : _max); - memcpy(dst, src, num); - dst[num] = '\0'; - - return dst; -} - -_DMON_PRIVATE char* dmon__unixpath(char* dst, int size, const char* path) -{ - size_t len = strlen(path); - len = dmon__min(len, (size_t)size - 1); - - for (size_t i = 0; i < len; i++) { - if (path[i] != '\\') - dst[i] = path[i]; - else - dst[i] = '/'; - } - dst[len] = '\0'; - return dst; -} - -#if DMON_OS_LINUX || DMON_OS_MACOS -_DMON_PRIVATE char* dmon__strcat(char* dst, int dst_sz, const char* src) -{ - int len = (int)strlen(dst); - return dmon__strcpy(dst + len, dst_sz - len, src); -} -#endif // DMON_OS_LINUX || DMON_OS_MACOS - -// stretchy buffer: https://github.com/nothings/stb/blob/master/stretchy_buffer.h -#define stb_sb_free(a) ((a) ? DMON_FREE(stb__sbraw(a)),0 : 0) -#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) -#define stb_sb_pop(a) (stb__sbn(a)--) -#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0) -#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) -#define stb_sb_last(a) ((a)[stb__sbn(a)-1]) -#define stb_sb_reset(a) ((a) ? (stb__sbn(a) = 0) : 0) - -#define stb__sbraw(a) ((int *) (a) - 2) -#define stb__sbm(a) stb__sbraw(a)[0] -#define stb__sbn(a) stb__sbraw(a)[1] - -#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a)) -#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) -#define stb__sbgrow(a,n) (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a)))) - -static void * stb__sbgrowf(void *arr, int increment, int itemsize) -{ - int dbl_cur = arr ? 2*stb__sbm(arr) : 0; - int min_needed = stb_sb_count(arr) + increment; - int m = dbl_cur > min_needed ? dbl_cur : min_needed; - int *p = (int *) DMON_REALLOC(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2); - if (p) { - if (!arr) - p[1] = 0; - p[0] = m; - return p+2; - } else { - return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later - } -} - -// watcher callback (same as dmon.h's decleration) -typedef void (dmon__watch_cb)(dmon_watch_id, dmon_action, const char*, const char*, const char*, void*); - -#if DMON_OS_WINDOWS -// IOCP (windows) -#ifdef UNICODE -# define _DMON_WINAPI_STR(name, size) wchar_t _##name[size]; MultiByteToWideChar(CP_UTF8, 0, name, -1, _##name, size) -#else -# define _DMON_WINAPI_STR(name, size) const char* _##name = name -#endif - -typedef struct dmon__win32_event { - char filepath[DMON_MAX_PATH]; - DWORD action; - dmon_watch_id watch_id; - bool skip; -} dmon__win32_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - OVERLAPPED overlapped; - HANDLE dir_handle; - uint8_t buffer[64512]; // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx - DWORD notify_filter; - dmon__watch_cb* watch_cb; - uint32_t watch_flags; - void* user_data; - char rootdir[DMON_MAX_PATH]; - char old_filepath[DMON_MAX_PATH]; -} dmon__watch_state; - -typedef struct dmon__state { - int num_watches; - dmon__watch_state watches[DMON_MAX_WATCHES]; - HANDLE thread_handle; - CRITICAL_SECTION mutex; - volatile LONG modify_watches; - dmon__win32_event* events; - bool quit; -} dmon__state; - -static bool _dmon_init; -static dmon__state _dmon; - -_DMON_PRIVATE bool dmon__refresh_watch(dmon__watch_state* watch) -{ - return ReadDirectoryChangesW(watch->dir_handle, watch->buffer, sizeof(watch->buffer), - (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) ? TRUE : FALSE, - watch->notify_filter, NULL, &watch->overlapped, NULL) != 0; -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - CancelIo(watch->dir_handle); - CloseHandle(watch->overlapped.hEvent); - CloseHandle(watch->dir_handle); - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -_DMON_PRIVATE void dmon__win32_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__win32_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - if (ev->action == FILE_ACTION_MODIFIED || ev->action == FILE_ACTION_ADDED) { - // remove duplicate modifies on a single file - for (int j = i + 1; j < c; j++) { - dmon__win32_event* check_ev = &_dmon.events[j]; - if (check_ev->action == FILE_ACTION_MODIFIED && - strcmp(ev->filepath, check_ev->filepath) == 0) { - check_ev->skip = true; - } - } - } - } - - // trigger user callbacks - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__win32_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - switch (ev->action) { - case FILE_ACTION_ADDED: - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - case FILE_ACTION_MODIFIED: - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - case FILE_ACTION_RENAMED_OLD_NAME: { - // find the first occurance of the NEW_NAME - // this is somewhat API flaw that we have no reference for relating old and new files - for (int j = i + 1; j < c; j++) { - dmon__win32_event* check_ev = &_dmon.events[j]; - if (check_ev->action == FILE_ACTION_RENAMED_NEW_NAME) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } break; - case FILE_ACTION_REMOVED: - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, - watch->user_data); - break; - } - } - stb_sb_reset(_dmon.events); -} - -_DMON_PRIVATE DWORD WINAPI dmon__thread(LPVOID arg) -{ - _DMON_UNUSED(arg); - HANDLE wait_handles[DMON_MAX_WATCHES]; - - SYSTEMTIME starttm; - GetSystemTime(&starttm); - uint64_t msecs_elapsed = 0; - - while (!_dmon.quit) { - if (_dmon.modify_watches || !TryEnterCriticalSection(&_dmon.mutex)) { - Sleep(10); - continue; - } - - if (_dmon.num_watches == 0) { - Sleep(10); - LeaveCriticalSection(&_dmon.mutex); - continue; - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - wait_handles[i] = watch->overlapped.hEvent; - } - - DWORD wait_result = WaitForMultipleObjects(_dmon.num_watches, wait_handles, FALSE, 10); - DMON_ASSERT(wait_result != WAIT_FAILED); - if (wait_result != WAIT_TIMEOUT) { - dmon__watch_state* watch = &_dmon.watches[wait_result - WAIT_OBJECT_0]; - DMON_ASSERT(HasOverlappedIoCompleted(&watch->overlapped)); - - DWORD bytes; - if (GetOverlappedResult(watch->dir_handle, &watch->overlapped, &bytes, FALSE)) { - char filepath[DMON_MAX_PATH]; - PFILE_NOTIFY_INFORMATION notify; - size_t offset = 0; - - if (bytes == 0) { - dmon__refresh_watch(watch); - LeaveCriticalSection(&_dmon.mutex); - continue; - } - - do { - notify = (PFILE_NOTIFY_INFORMATION)&watch->buffer[offset]; - - int count = WideCharToMultiByte(CP_UTF8, 0, notify->FileName, - notify->FileNameLength / sizeof(WCHAR), - filepath, DMON_MAX_PATH - 1, NULL, NULL); - 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; - } - dmon__win32_event wev = { { 0 }, notify->Action, watch->id, false }; - dmon__strcpy(wev.filepath, sizeof(wev.filepath), filepath); - stb_sb_push(_dmon.events, wev); - - offset += notify->NextEntryOffset; - } while (notify->NextEntryOffset > 0); - - if (!_dmon.quit) { - dmon__refresh_watch(watch); - } - } - } // if (WaitForMultipleObjects) - - SYSTEMTIME tm; - GetSystemTime(&tm); - LONG dt = - (tm.wSecond - starttm.wSecond) * 1000 + (tm.wMilliseconds - starttm.wMilliseconds); - starttm = tm; - msecs_elapsed += dt; - if (msecs_elapsed > 100 && stb_sb_count(_dmon.events) > 0) { - dmon__win32_process_events(); - msecs_elapsed = 0; - } - - LeaveCriticalSection(&_dmon.mutex); - } - return 0; -} - - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - InitializeCriticalSection(&_dmon.mutex); - - _dmon.thread_handle = - CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)dmon__thread, NULL, 0, NULL); - DMON_ASSERT(_dmon.thread_handle); - _dmon_init = true; -} - - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - if (_dmon.thread_handle != INVALID_HANDLE_VALUE) { - WaitForSingleObject(_dmon.thread_handle, INFINITE); - CloseHandle(_dmon.thread_handle); - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - DeleteCriticalSection(&_dmon.mutex); - stb_sb_free(_dmon.events); - _dmon_init = false; -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data) -{ - 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); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - dmon__unixpath(watch->rootdir, sizeof(watch->rootdir), rootdir); - size_t 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); - } - - LeaveCriticalSection(&_dmon.mutex); - _InterlockedExchange(&_dmon.modify_watches, 0); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - _InterlockedExchange(&_dmon.modify_watches, 1); - EnterCriticalSection(&_dmon.mutex); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - LeaveCriticalSection(&_dmon.mutex); - _InterlockedExchange(&_dmon.modify_watches, 0); -} - -#elif DMON_OS_LINUX -// inotify linux backend -#define _DMON_TEMP_BUFFSIZE ((sizeof(struct inotify_event) + PATH_MAX) * 1024) - -typedef struct dmon__watch_subdir { - char rootdir[DMON_MAX_PATH]; -} dmon__watch_subdir; - -typedef struct dmon__inotify_event { - char filepath[DMON_MAX_PATH]; - uint32_t mask; - uint32_t cookie; - dmon_watch_id watch_id; - bool skip; -} dmon__inotify_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - int fd; - uint32_t watch_flags; - dmon__watch_cb* watch_cb; - void* user_data; - char rootdir[DMON_MAX_PATH]; - dmon__watch_subdir* subdirs; - int* wds; -} dmon__watch_state; - -typedef struct dmon__state { - dmon__watch_state watches[DMON_MAX_WATCHES]; - dmon__inotify_event* events; - int num_watches; - pthread_t thread_handle; - pthread_mutex_t mutex; - bool quit; -} dmon__state; - -static bool _dmon_init; -static dmon__state _dmon; - -_DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t mask, - bool followlinks, dmon__watch_state* watch) -{ - struct dirent* entry; - DIR* dir = opendir(dirname); - DMON_ASSERT(dir); - - char watchdir[DMON_MAX_PATH]; - - while ((entry = readdir(dir)) != NULL) { - bool entry_valid = false; - if (entry->d_type == DT_DIR) { - if (strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, ".") != 0) { - dmon__strcpy(watchdir, sizeof(watchdir), dirname); - dmon__strcat(watchdir, sizeof(watchdir), entry->d_name); - entry_valid = true; - } - } else if (followlinks && entry->d_type == DT_LNK) { - char linkpath[PATH_MAX]; - dmon__strcpy(watchdir, sizeof(watchdir), dirname); - dmon__strcat(watchdir, sizeof(watchdir), entry->d_name); - char* r = realpath(watchdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - dmon__strcpy(watchdir, sizeof(watchdir), linkpath); - entry_valid = true; - } - - // add sub-directory to watch dirs - if (entry_valid) { - int watchdir_len = (int)strlen(watchdir); - if (watchdir[watchdir_len - 1] != '/') { - watchdir[watchdir_len] = '/'; - watchdir[watchdir_len + 1] = '\0'; - } - int wd = inotify_add_watch(fd, watchdir, mask); - _DMON_UNUSED(wd); - DMON_ASSERT(wd != -1); - - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // recurse - dmon__watch_recursive(watchdir, fd, mask, followlinks, watch); - } - } - closedir(dir); -} - -_DMON_PRIVATE const char* dmon__find_subdir(const dmon__watch_state* watch, int wd) -{ - const int* wds = watch->wds; - for (int i = 0, c = stb_sb_count(wds); i < c; i++) { - if (wd == wds[i]) { - return watch->subdirs[i].rootdir; - } - } - - return NULL; -} - -_DMON_PRIVATE void dmon__gather_recursive(dmon__watch_state* watch, const char* dirname) -{ - struct dirent* entry; - DIR* dir = opendir(dirname); - DMON_ASSERT(dir); - - char newdir[DMON_MAX_PATH]; - while ((entry = readdir(dir)) != NULL) { - bool entry_valid = false; - bool is_dir = false; - if (strcmp(entry->d_name, "..") != 0 && strcmp(entry->d_name, ".") != 0) { - dmon__strcpy(newdir, sizeof(newdir), dirname); - dmon__strcat(newdir, sizeof(newdir), entry->d_name); - is_dir = (entry->d_type == DT_DIR); - entry_valid = true; - } - - // add sub-directory to watch dirs - if (entry_valid) { - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), newdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), newdir + strlen(watch->rootdir)); - } - - dmon__inotify_event dev = { { 0 }, IN_CREATE|(is_dir ? IN_ISDIR : 0), 0, watch->id, false }; - dmon__strcpy(dev.filepath, sizeof(dev.filepath), subdir.rootdir); - stb_sb_push(_dmon.events, dev); - } - } - closedir(dir); -} - -_DMON_PRIVATE void dmon__inotify_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__inotify_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - // remove redundant modify events on a single file - if (ev->mask & IN_MODIFY) { - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } else if ((ev->mask & IN_ISDIR) && (check_ev->mask & (IN_ISDIR|IN_MODIFY))) { - // 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 traling 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'; - if (check_ev->filepath[l2-1] == '/') check_ev->filepath[l2-1] = '\0'; - if (strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } - } - } - } else if (ev->mask & IN_CREATE) { - bool loop_break = false; - for (int 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) { - // 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 - for (int 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; // change to modified - ev->skip = check_ev->skip = true; - loop_break = true; - break; - } - } - } 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; - } - } - } else if (ev->mask & IN_MOVED_FROM) { - bool move_valid = false; - for (int j = i + 1; j < c; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) { - move_valid = true; - break; - } - } - - // 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; - } - } else if (ev->mask & IN_MOVED_TO) { - bool move_valid = false; - for (int j = 0; j < i; j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_FROM && ev->cookie == check_ev->cookie) { - move_valid = true; - break; - } - } - - // 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; - } - } else if (ev->mask & IN_DELETE) { - for (int 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; - } - } - } - } - - // trigger user callbacks - for (int i = 0; i < stb_sb_count(_dmon.events); i++) { - dmon__inotify_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - if (ev->mask & IN_CREATE) { - if (ev->mask & IN_ISDIR) { - if (watch->watch_flags & DMON_WATCHFLAGS_RECURSIVE) { - char watchdir[DMON_MAX_PATH]; - dmon__strcpy(watchdir, sizeof(watchdir), watch->rootdir); - dmon__strcat(watchdir, sizeof(watchdir), ev->filepath); - dmon__strcat(watchdir, sizeof(watchdir), "/"); - uint32_t mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - int wd = inotify_add_watch(watch->fd, watchdir, mask); - _DMON_UNUSED(wd); - DMON_ASSERT(wd != -1); - - dmon__watch_subdir subdir; - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - // 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]; // 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); - } - else if (ev->mask & IN_MODIFY) { - watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - else if (ev->mask & IN_MOVED_FROM) { - for (int j = i + 1; j < stb_sb_count(_dmon.events); j++) { - dmon__inotify_event* check_ev = &_dmon.events[j]; - if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } - else if (ev->mask & IN_DELETE) { - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, watch->user_data); - } - } - - stb_sb_reset(_dmon.events); -} - -static void* dmon__thread(void* arg) -{ - _DMON_UNUSED(arg); - - static uint8_t buff[_DMON_TEMP_BUFFSIZE]; - struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; - struct timespec rem = { 0, 0 }; - struct timeval timeout; - uint64_t usecs_elapsed = 0; - - struct timeval starttm; - gettimeofday(&starttm, 0); - - while (!_dmon.quit) { - nanosleep(&req, &rem); - if (_dmon.num_watches == 0 || pthread_mutex_trylock(&_dmon.mutex) != 0) { - continue; - } - - // Create read FD set - fd_set rfds; - FD_ZERO(&rfds); - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - FD_SET(watch->fd, &rfds); - } - - timeout.tv_sec = 0; - timeout.tv_usec = 100000; - if (select(FD_SETSIZE, &rfds, NULL, NULL, &timeout)) { - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - if (FD_ISSET(watch->fd, &rfds)) { - ssize_t offset = 0; - ssize_t len = read(watch->fd, buff, _DMON_TEMP_BUFFSIZE); - if (len <= 0) { - continue; - } - - while (offset < len) { - struct inotify_event* iev = (struct inotify_event*)&buff[offset]; - - const char *subdir = dmon__find_subdir(watch, iev->wd); - if (subdir) { - char filepath[DMON_MAX_PATH]; - 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; - } - dmon__inotify_event dev = { { 0 }, iev->mask, iev->cookie, watch->id, false }; - dmon__strcpy(dev.filepath, sizeof(dev.filepath), filepath); - stb_sb_push(_dmon.events, dev); - } - - offset += sizeof(struct inotify_event) + iev->len; - } - } - } - } - - struct timeval tm; - gettimeofday(&tm, 0); - long dt = (tm.tv_sec - starttm.tv_sec) * 1000000 + tm.tv_usec - starttm.tv_usec; - starttm = tm; - usecs_elapsed += dt; - if (usecs_elapsed > 100000 && stb_sb_count(_dmon.events) > 0) { - dmon__inotify_process_events(); - usecs_elapsed = 0; - } - - pthread_mutex_unlock(&_dmon.mutex); - } - return 0x0; -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - close(watch->fd); - stb_sb_free(watch->subdirs); - stb_sb_free(watch->wds); - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - pthread_mutex_init(&_dmon.mutex, NULL); - - int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL); - _DMON_UNUSED(r); - DMON_ASSERT(r == 0 && "pthread_create failed"); - _dmon_init = true; -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - pthread_join(_dmon.thread_handle, NULL); - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - pthread_mutex_destroy(&_dmon.mutex); - stb_sb_free(_dmon.events); - _dmon_init = false; -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data) -{ - DMON_ASSERT(watch_cb); - DMON_ASSERT(rootdir && rootdir[0]); - - pthread_mutex_lock(&_dmon.mutex); - - DMON_ASSERT(_dmon.num_watches < DMON_MAX_WATCHES); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - struct stat root_st; - if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || - (root_st.st_mode & S_IRUSR) != S_IRUSR) { - _DMON_LOG_ERRORF("Could not open/read directory: %s", rootdir); - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - - - if (S_ISLNK(root_st.st_mode)) { - if (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) { - char linkpath[PATH_MAX]; - char* r = realpath(rootdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); - } else { - _DMON_LOG_ERRORF("symlinks are unsupported: %s. use DMON_WATCHFLAGS_FOLLOW_SYMLINKS", - rootdir); - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - } else { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - } - - // add trailing slash - int rootdir_len = (int)strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - watch->fd = inotify_init(); - if (watch->fd < -1) { - DMON_LOG_ERROR("could not create inotify instance"); - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - - uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - int wd = inotify_add_watch(watch->fd, watch->rootdir, inotify_mask); - if (wd < 0) { - _DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watch->rootdir, errno); - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(0); - } - dmon__watch_subdir subdir; - 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); - - // recursive mode: enumarate all child directories and add them to watch - if (flags & DMON_WATCHFLAGS_RECURSIVE) { - dmon__watch_recursive(watch->rootdir, watch->fd, inotify_mask, - (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) ? true : false, watch); - } - - - pthread_mutex_unlock(&_dmon.mutex); - return dmon__make_id(id); -} - -DMON_API_IMPL void dmon_unwatch(dmon_watch_id id) -{ - DMON_ASSERT(id.id > 0); - - pthread_mutex_lock(&_dmon.mutex); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - pthread_mutex_unlock(&_dmon.mutex); -} -#elif DMON_OS_MACOS -// FSEvents MacOS backend -typedef struct dmon__fsevent_event { - char filepath[DMON_MAX_PATH]; - uint64_t event_id; - long event_flags; - dmon_watch_id watch_id; - bool skip; - bool move_valid; -} dmon__fsevent_event; - -typedef struct dmon__watch_state { - dmon_watch_id id; - uint32_t watch_flags; - FSEventStreamRef fsev_stream_ref; - dmon__watch_cb* watch_cb; - void* user_data; - char rootdir[DMON_MAX_PATH]; - char rootdir_unmod[DMON_MAX_PATH]; - bool init; -} dmon__watch_state; - -typedef struct dmon__state { - dmon__watch_state watches[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; - CFRunLoopRef cf_loop_ref; - CFAllocatorRef cf_alloc_ref; - bool quit; -} dmon__state; - -union dmon__cast_userdata { - void* ptr; - uint32_t id; -}; - -static bool _dmon_init; -static dmon__state _dmon; - -_DMON_PRIVATE void* dmon__cf_malloc(CFIndex size, CFOptionFlags hints, void* info) -{ - _DMON_UNUSED(hints); - _DMON_UNUSED(info); - return DMON_MALLOC(size); -} - -_DMON_PRIVATE void dmon__cf_free(void* ptr, void* info) -{ - _DMON_UNUSED(info); - DMON_FREE(ptr); -} - -_DMON_PRIVATE void* dmon__cf_realloc(void* ptr, CFIndex newsize, CFOptionFlags hints, void* info) -{ - _DMON_UNUSED(hints); - _DMON_UNUSED(info); - return DMON_REALLOC(ptr, (size_t)newsize); -} - -_DMON_PRIVATE void dmon__fsevent_process_events(void) -{ - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__fsevent_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - - // remove redundant modify events on a single file - if (ev->event_flags & kFSEventStreamEventFlagItemModified) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if ((check_ev->event_flags & kFSEventStreamEventFlagItemModified) && - strcmp(ev->filepath, check_ev->filepath) == 0) { - ev->skip = true; - break; - } - } - } else if ((ev->event_flags & kFSEventStreamEventFlagItemRenamed) && !ev->move_valid) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if ((check_ev->event_flags & kFSEventStreamEventFlagItemRenamed) && - check_ev->event_id == (ev->event_id + 1)) { - ev->move_valid = check_ev->move_valid = true; - break; - } - } - - // 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; - - char abs_filepath[DMON_MAX_PATH]; - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id-1]; - dmon__strcpy(abs_filepath, sizeof(abs_filepath), watch->rootdir); - dmon__strcat(abs_filepath, sizeof(abs_filepath), ev->filepath); - - struct stat root_st; - if (stat(abs_filepath, &root_st) != 0) { - ev->event_flags |= kFSEventStreamEventFlagItemRemoved; - } else { - ev->event_flags |= kFSEventStreamEventFlagItemCreated; - } - } - } - } - - // trigger user callbacks - for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) { - dmon__fsevent_event* ev = &_dmon.events[i]; - if (ev->skip) { - continue; - } - dmon__watch_state* watch = &_dmon.watches[ev->watch_id.id - 1]; - - if(watch == NULL || watch->watch_cb == NULL) { - continue; - } - - if (ev->event_flags & kFSEventStreamEventFlagItemCreated) { - watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } else 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) { - for (int j = i + 1; j < c; j++) { - dmon__fsevent_event* check_ev = &_dmon.events[j]; - if (check_ev->event_flags & kFSEventStreamEventFlagItemRenamed) { - watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir_unmod, - check_ev->filepath, ev->filepath, watch->user_data); - break; - } - } - } else if (ev->event_flags & kFSEventStreamEventFlagItemRemoved) { - watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir_unmod, ev->filepath, NULL, - watch->user_data); - } - } - - stb_sb_reset(_dmon.events); -} - -static void* dmon__thread(void* arg) -{ - _DMON_UNUSED(arg); - - struct timespec req = { (time_t)10 / 1000, (long)(10 * 1000000) }; - struct timespec rem = { 0, 0 }; - - _dmon.cf_loop_ref = CFRunLoopGetCurrent(); - dispatch_semaphore_signal(_dmon.thread_sem); - - while (!_dmon.quit) { - if (_dmon.modify_watches || pthread_mutex_trylock(&_dmon.mutex) != 0) { - nanosleep(&req, &rem); - continue; - } - - if (_dmon.num_watches == 0) { - nanosleep(&req, &rem); - pthread_mutex_unlock(&_dmon.mutex); - continue; - } - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__watch_state* watch = &_dmon.watches[i]; - if (!watch->init) { - DMON_ASSERT(watch->fsev_stream_ref); - FSEventStreamScheduleWithRunLoop(watch->fsev_stream_ref, _dmon.cf_loop_ref, - kCFRunLoopDefaultMode); - FSEventStreamStart(watch->fsev_stream_ref); - - watch->init = true; - } - } - - CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.5, kCFRunLoopRunTimedOut); - dmon__fsevent_process_events(); - - pthread_mutex_unlock(&_dmon.mutex); - } - - CFRunLoopStop(_dmon.cf_loop_ref); - _dmon.cf_loop_ref = NULL; - return 0x0; -} - -_DMON_PRIVATE void dmon__unwatch(dmon__watch_state* watch) -{ - if (watch->fsev_stream_ref) { - FSEventStreamStop(watch->fsev_stream_ref); - FSEventStreamInvalidate(watch->fsev_stream_ref); - FSEventStreamRelease(watch->fsev_stream_ref); - watch->fsev_stream_ref = NULL; - } - - memset(watch, 0x0, sizeof(dmon__watch_state)); -} - -DMON_API_IMPL void dmon_init(void) -{ - DMON_ASSERT(!_dmon_init); - pthread_mutex_init(&_dmon.mutex, NULL); - - CFAllocatorContext cf_alloc_ctx = { 0 }; - cf_alloc_ctx.allocate = dmon__cf_malloc; - cf_alloc_ctx.deallocate = dmon__cf_free; - cf_alloc_ctx.reallocate = dmon__cf_realloc; - _dmon.cf_alloc_ref = CFAllocatorCreate(NULL, &cf_alloc_ctx); - - _dmon.thread_sem = dispatch_semaphore_create(0); - DMON_ASSERT(_dmon.thread_sem); - - int r = pthread_create(&_dmon.thread_handle, NULL, dmon__thread, NULL); - _DMON_UNUSED(r); - DMON_ASSERT(r == 0 && "pthread_create failed"); - - // wait for thread to initialize loop object - dispatch_semaphore_wait(_dmon.thread_sem, DISPATCH_TIME_FOREVER); - - _dmon_init = true; -} - -DMON_API_IMPL void dmon_deinit(void) -{ - DMON_ASSERT(_dmon_init); - _dmon.quit = true; - pthread_join(_dmon.thread_handle, NULL); - - dispatch_release(_dmon.thread_sem); - - for (int i = 0; i < _dmon.num_watches; i++) { - dmon__unwatch(&_dmon.watches[i]); - } - - pthread_mutex_destroy(&_dmon.mutex); - stb_sb_free(_dmon.events); - if (_dmon.cf_alloc_ref) { - CFRelease(_dmon.cf_alloc_ref); - } - - _dmon_init = false; -} - -_DMON_PRIVATE void dmon__fsevent_callback(ConstFSEventStreamRef stream_ref, void* user_data, - size_t num_events, void* event_paths, - const FSEventStreamEventFlags event_flags[], - const FSEventStreamEventId event_ids[]) -{ - _DMON_UNUSED(stream_ref); - - union dmon__cast_userdata _userdata; - _userdata.ptr = user_data; - dmon_watch_id watch_id = dmon__make_id(_userdata.id); - DMON_ASSERT(watch_id.id > 0); - dmon__watch_state* watch = &_dmon.watches[watch_id.id - 1]; - char abs_filepath[DMON_MAX_PATH]; - char abs_filepath_lower[DMON_MAX_PATH]; - - for (size_t i = 0; i < num_events; i++) { - const char* filepath = ((const char**)event_paths)[i]; - long flags = (long)event_flags[i]; - uint64_t event_id = (uint64_t)event_ids[i]; - dmon__fsevent_event ev; - memset(&ev, 0x0, sizeof(ev)); - - dmon__strcpy(abs_filepath, sizeof(abs_filepath), filepath); - dmon__unixpath(abs_filepath, sizeof(abs_filepath), 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 begining - dmon__strcpy(ev.filepath, sizeof(ev.filepath), abs_filepath + strlen(watch->rootdir)); - - ev.event_flags = flags; - ev.event_id = event_id; - ev.watch_id = watch_id; - stb_sb_push(_dmon.events, ev); - } -} - -DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir, - void (*watch_cb)(dmon_watch_id watch_id, dmon_action action, - const char* dirname, const char* filename, - const char* oldname, void* user), - uint32_t flags, void* user_data) -{ - 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); - - uint32_t id = ++_dmon.num_watches; - dmon__watch_state* watch = &_dmon.watches[id - 1]; - watch->id = dmon__make_id(id); - watch->watch_flags = flags; - watch->watch_cb = watch_cb; - watch->user_data = user_data; - - struct stat root_st; - if (stat(rootdir, &root_st) != 0 || !S_ISDIR(root_st.st_mode) || - (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); - } - - if (S_ISLNK(root_st.st_mode)) { - if (flags & DMON_WATCHFLAGS_FOLLOW_SYMLINKS) { - char linkpath[PATH_MAX]; - char* r = realpath(rootdir, linkpath); - _DMON_UNUSED(r); - DMON_ASSERT(r); - - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, linkpath); - } 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 { - char rootdir_abspath[DMON_MAX_PATH]; - if (realpath(rootdir, rootdir_abspath) != NULL) { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir_abspath); - } else { - dmon__strcpy(watch->rootdir, sizeof(watch->rootdir) - 1, rootdir); - } - } - - dmon__unixpath(watch->rootdir, sizeof(watch->rootdir), watch->rootdir); - - // add trailing slash - int rootdir_len = (int)strlen(watch->rootdir); - if (watch->rootdir[rootdir_len - 1] != '/') { - watch->rootdir[rootdir_len] = '/'; - watch->rootdir[rootdir_len + 1] = '\0'; - } - - dmon__strcpy(watch->rootdir_unmod, sizeof(watch->rootdir_unmod), watch->rootdir); - dmon__tolower(watch->rootdir, sizeof(watch->rootdir), watch->rootdir); - - // create FS objects - CFStringRef cf_dir = CFStringCreateWithCString(NULL, watch->rootdir_unmod, kCFStringEncodingUTF8); - CFArrayRef cf_dirarr = CFArrayCreate(NULL, (const void**)&cf_dir, 1, NULL); - - FSEventStreamContext ctx; - union dmon__cast_userdata userdata; - userdata.id = id; - ctx.version = 0; - ctx.info = userdata.ptr; - ctx.retain = NULL; - ctx.release = NULL; - ctx.copyDescription = NULL; - watch->fsev_stream_ref = FSEventStreamCreate(_dmon.cf_alloc_ref, dmon__fsevent_callback, &ctx, - 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(id.id > 0); - - __sync_lock_test_and_set(&_dmon.modify_watches, 1); - pthread_mutex_lock(&_dmon.mutex); - - int index = id.id - 1; - DMON_ASSERT(index < _dmon.num_watches); - - dmon__unwatch(&_dmon.watches[index]); - if (index != _dmon.num_watches - 1) { - dmon__swap(_dmon.watches[index], _dmon.watches[_dmon.num_watches - 1], dmon__watch_state); - } - --_dmon.num_watches; - - pthread_mutex_unlock(&_dmon.mutex); - __sync_lock_test_and_set(&_dmon.modify_watches, 0); -} - -#endif - -#endif // DMON_IMPL -#endif // __DMON_H__ diff --git a/src/dmon_extra.h b/src/dmon_extra.h deleted file mode 100644 index 4b321034..00000000 --- a/src/dmon_extra.h +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef __DMON_EXTRA_H__ -#define __DMON_EXTRA_H__ - -// -// Copyright 2021 Sepehr Taghdisian (septag@github). All rights reserved. -// License: https://github.com/septag/dmon#license-bsd-2-clause -// -// Extra header functionality for dmon.h for the backend based on inotify -// -// Add/Remove directory functions: -// dmon_watch_add: Adds a sub-directory to already valid watch_id. sub-directories are assumed to be relative to watch root_dir -// dmon_watch_add: Removes a sub-directory from already valid watch_id. sub-directories are assumed to be relative to watch root_dir -// Reason: The inotify backend does not work well when watching in recursive mode a root directory of a large tree, it may take -// quite a while until all inotify watches are established, and events will not be received in this time. Also, since one -// inotify watch will be established per subdirectory, it is possible that the maximum amount of inotify watches per user -// will be reached. The default maximum is 8192. -// When using inotify backend users may turn off the DMON_WATCHFLAGS_RECURSIVE flag and add/remove selectively the -// sub-directories to be watched based on application-specific logic about which sub-directory actually needs to be watched. -// The function dmon_watch_add and dmon_watch_rm are used to this purpose. -// - -#ifndef __DMON_H__ -#error "Include 'dmon.h' before including this file" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -DMON_API_DECL bool dmon_watch_add(dmon_watch_id id, const char* subdir); -DMON_API_DECL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir); - -#ifdef __cplusplus -} -#endif - -#ifdef DMON_IMPL -#if DMON_OS_LINUX -DMON_API_IMPL bool dmon_watch_add(dmon_watch_id id, const char* watchdir) -{ - DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES); - - bool skip_lock = pthread_self() == _dmon.thread_handle; - - if (!skip_lock) - pthread_mutex_lock(&_dmon.mutex); - - dmon__watch_state* watch = &_dmon.watches[id.id - 1]; - - // check if the directory exists - // if watchdir contains absolute/root-included path, try to strip the rootdir from it - // else, we assume that watchdir is correct, so save it as it is - struct stat st; - dmon__watch_subdir subdir; - if (stat(watchdir, &st) == 0 && (st.st_mode & S_IFDIR)) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - if (strstr(subdir.rootdir, watch->rootdir) == subdir.rootdir) { - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir + strlen(watch->rootdir)); - } - } else { - char fullpath[DMON_MAX_PATH]; - dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); - dmon__strcat(fullpath, sizeof(fullpath), watchdir); - if (stat(fullpath, &st) != 0 || (st.st_mode & S_IFDIR) == 0) { - _DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir); - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir); - } - - int dirlen = (int)strlen(subdir.rootdir); - if (subdir.rootdir[dirlen - 1] != '/') { - subdir.rootdir[dirlen] = '/'; - subdir.rootdir[dirlen + 1] = '\0'; - } - - // check that the directory is not already added - for (int i = 0, c = stb_sb_count(watch->subdirs); i < c; i++) { - if (strcmp(subdir.rootdir, watch->subdirs[i].rootdir) == 0) { - _DMON_LOG_ERRORF("Error watching directory '%s', because it is already added.", watchdir); - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - } - - const uint32_t inotify_mask = IN_MOVED_TO | IN_CREATE | IN_MOVED_FROM | IN_DELETE | IN_MODIFY; - char fullpath[DMON_MAX_PATH]; - dmon__strcpy(fullpath, sizeof(fullpath), watch->rootdir); - dmon__strcat(fullpath, sizeof(fullpath), subdir.rootdir); - int wd = inotify_add_watch(watch->fd, fullpath, inotify_mask); - if (wd == -1) { - _DMON_LOG_ERRORF("Error watching directory '%s'. (inotify_add_watch:err=%d)", watchdir, errno); - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - - stb_sb_push(watch->subdirs, subdir); - stb_sb_push(watch->wds, wd); - - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - - return true; -} - -DMON_API_IMPL bool dmon_watch_rm(dmon_watch_id id, const char* watchdir) -{ - DMON_ASSERT(id.id > 0 && id.id <= DMON_MAX_WATCHES); - - bool skip_lock = pthread_self() == _dmon.thread_handle; - - if (!skip_lock) - pthread_mutex_lock(&_dmon.mutex); - - dmon__watch_state* watch = &_dmon.watches[id.id - 1]; - - char subdir[DMON_MAX_PATH]; - dmon__strcpy(subdir, sizeof(subdir), watchdir); - if (strstr(subdir, watch->rootdir) == subdir) { - dmon__strcpy(subdir, sizeof(subdir), watchdir + strlen(watch->rootdir)); - } - - int dirlen = (int)strlen(subdir); - if (subdir[dirlen - 1] != '/') { - subdir[dirlen] = '/'; - subdir[dirlen + 1] = '\0'; - } - - int i, c = stb_sb_count(watch->subdirs); - for (i = 0; i < c; i++) { - if (strcmp(watch->subdirs[i].rootdir, subdir) == 0) { - break; - } - } - if (i >= c) { - _DMON_LOG_ERRORF("Watch directory '%s' is not valid", watchdir); - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return false; - } - inotify_rm_watch(watch->fd, watch->wds[i]); - - /* Remove entry from subdirs and wds by swapping position with the last entry */ - watch->subdirs[i] = stb_sb_last(watch->subdirs); - stb_sb_pop(watch->subdirs); - - watch->wds[i] = stb_sb_last(watch->wds); - stb_sb_pop(watch->wds); - - if (!skip_lock) - pthread_mutex_unlock(&_dmon.mutex); - return true; -} -#endif // DMON_OS_LINUX -#endif // DMON_IMPL - -#endif // __DMON_EXTRA_H__ - diff --git a/src/meson.build b/src/meson.build index 2da04fda..503fdc48 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,11 +22,11 @@ elif host_machine.system() == 'darwin' lite_sources += 'bundle_open.m' endif -lite_include = include_directories('.') +lite_includes += include_directories('.') executable('lite-xl', lite_sources + lite_rc, - include_directories: [lite_include, font_renderer_include], + include_directories: lite_includes, dependencies: lite_deps, c_args: lite_cargs, objc_args: lite_cargs, |
