Update dmon from septag/dmon commit 74bbd93b
The new version includes fixes from jgmdev, github PR: https://github.com/septag/dmon/pull/11 to solve incorrect behavior on linux not reporting directory creation. Includes also a further revision from septag.
This commit is contained in:
parent
becd817ec4
commit
cb4c7d397d
130
src/dmon.h
130
src/dmon.h
|
@ -6,23 +6,23 @@
|
||||||
// watches directories for file or directory changes.
|
// watches directories for file or directory changes.
|
||||||
//
|
//
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// Usage:
|
// Usage:
|
||||||
// define DMON_IMPL and include this file to use it:
|
// define DMON_IMPL and include this file to use it:
|
||||||
// #define DMON_IMPL
|
// #define DMON_IMPL
|
||||||
// #include "dmon.h"
|
// #include "dmon.h"
|
||||||
//
|
//
|
||||||
// dmon_init():
|
// dmon_init():
|
||||||
// Call this once at the start of your program.
|
// Call this once at the start of your program.
|
||||||
// This will start a low-priority monitoring thread
|
// This will start a low-priority monitoring thread
|
||||||
// dmon_deinit():
|
// dmon_deinit():
|
||||||
// Call this when your work with dmon is finished, usually on program terminate
|
// Call this when your work with dmon is finished, usually on program terminate
|
||||||
// This will free resources and stop the monitoring thread
|
// This will free resources and stop the monitoring thread
|
||||||
// dmon_watch:
|
// dmon_watch:
|
||||||
// Watch for directories
|
// Watch for directories
|
||||||
// You can watch multiple directories by calling this function multiple times
|
// You can watch multiple directories by calling this function multiple times
|
||||||
// rootdir: root directory to monitor
|
// rootdir: root directory to monitor
|
||||||
// watch_cb: callback function to receive events.
|
// watch_cb: callback function to receive events.
|
||||||
// NOTE that this function is called from another thread, so you should
|
// NOTE that this function is called from another thread, so you should
|
||||||
// beware of data races in your application when accessing data within this
|
// beware of data races in your application when accessing data within this
|
||||||
// callback
|
// callback
|
||||||
// flags: watch flags, see dmon_watch_flags_t
|
// flags: watch flags, see dmon_watch_flags_t
|
||||||
|
@ -35,12 +35,12 @@
|
||||||
//
|
//
|
||||||
// Configuration:
|
// Configuration:
|
||||||
// You can customize some low-level functionality like malloc and logging by overriding macros:
|
// You can customize some low-level functionality like malloc and logging by overriding macros:
|
||||||
//
|
//
|
||||||
// DMON_MALLOC, DMON_FREE, DMON_REALLOC:
|
// DMON_MALLOC, DMON_FREE, DMON_REALLOC:
|
||||||
// define these macros to override memory allocations
|
// define these macros to override memory allocations
|
||||||
// default is 'malloc', 'free' and 'realloc'
|
// default is 'malloc', 'free' and 'realloc'
|
||||||
// DMON_ASSERT:
|
// DMON_ASSERT:
|
||||||
// define this to provide your own assert
|
// define this to provide your own assert
|
||||||
// default is 'assert'
|
// default is 'assert'
|
||||||
// DMON_LOG_ERROR:
|
// DMON_LOG_ERROR:
|
||||||
// define this to provide your own logging mechanism
|
// define this to provide your own logging mechanism
|
||||||
|
@ -60,9 +60,9 @@
|
||||||
//
|
//
|
||||||
// TODO:
|
// TODO:
|
||||||
// - DMON_WATCHFLAGS_FOLLOW_SYMLINKS does not resolve files
|
// - DMON_WATCHFLAGS_FOLLOW_SYMLINKS does not resolve files
|
||||||
// - implement DMON_WATCHFLAGS_OUTOFSCOPE_LINKS
|
// - implement DMON_WATCHFLAGS_OUTOFSCOPE_LINKS
|
||||||
// - implement DMON_WATCHFLAGS_IGNORE_DIRECTORIES
|
// - implement DMON_WATCHFLAGS_IGNORE_DIRECTORIES
|
||||||
//
|
//
|
||||||
// History:
|
// History:
|
||||||
// 1.0.0 First version. working Win32/Linux backends
|
// 1.0.0 First version. working Win32/Linux backends
|
||||||
// 1.1.0 MacOS backend
|
// 1.1.0 MacOS backend
|
||||||
|
@ -86,9 +86,9 @@
|
||||||
|
|
||||||
typedef struct { uint32_t id; } dmon_watch_id;
|
typedef struct { uint32_t id; } dmon_watch_id;
|
||||||
|
|
||||||
// Pass these flags to `dmon_watch`
|
// Pass these flags to `dmon_watch`
|
||||||
typedef enum dmon_watch_flags_t {
|
typedef enum dmon_watch_flags_t {
|
||||||
DMON_WATCHFLAGS_RECURSIVE = 0x1, // monitor all child directories
|
DMON_WATCHFLAGS_RECURSIVE = 0x1, // monitor all child directories
|
||||||
DMON_WATCHFLAGS_FOLLOW_SYMLINKS = 0x2, // resolve symlinks (linux only)
|
DMON_WATCHFLAGS_FOLLOW_SYMLINKS = 0x2, // resolve symlinks (linux only)
|
||||||
DMON_WATCHFLAGS_OUTOFSCOPE_LINKS = 0x4, // TODO: not implemented yet
|
DMON_WATCHFLAGS_OUTOFSCOPE_LINKS = 0x4, // TODO: not implemented yet
|
||||||
DMON_WATCHFLAGS_IGNORE_DIRECTORIES = 0x8 // TODO: not implemented yet
|
DMON_WATCHFLAGS_IGNORE_DIRECTORIES = 0x8 // TODO: not implemented yet
|
||||||
|
@ -197,7 +197,7 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
|
||||||
# include <stdio.h>
|
# include <stdio.h>
|
||||||
# define DMON_LOG_DEBUG(s) do { puts(s); } while(0)
|
# define DMON_LOG_DEBUG(s) do { puts(s); } while(0)
|
||||||
# else
|
# else
|
||||||
# define DMON_LOG_DEBUG(s)
|
# define DMON_LOG_DEBUG(s)
|
||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ DMON_API_DECL void dmon_unwatch(dmon_watch_id id);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef dmon__min
|
#ifndef dmon__min
|
||||||
# define dmon__min(a, b) ((a) < (b) ? (a) : (b))
|
# define dmon__min(a, b) ((a) < (b) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef dmon__max
|
#ifndef dmon__max
|
||||||
|
@ -364,7 +364,7 @@ typedef void (dmon__watch_cb)(dmon_watch_id, dmon_action, const char*, const cha
|
||||||
typedef struct dmon__win32_event {
|
typedef struct dmon__win32_event {
|
||||||
char filepath[DMON_MAX_PATH];
|
char filepath[DMON_MAX_PATH];
|
||||||
DWORD action;
|
DWORD action;
|
||||||
dmon_watch_id watch_id;
|
dmon_watch_id watch_id;
|
||||||
bool skip;
|
bool skip;
|
||||||
} dmon__win32_event;
|
} dmon__win32_event;
|
||||||
|
|
||||||
|
@ -753,6 +753,10 @@ _DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t m
|
||||||
|
|
||||||
dmon__watch_subdir subdir;
|
dmon__watch_subdir subdir;
|
||||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watchdir);
|
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->subdirs, subdir);
|
||||||
stb_sb_push(watch->wds, wd);
|
stb_sb_push(watch->wds, wd);
|
||||||
|
|
||||||
|
@ -763,6 +767,19 @@ _DMON_PRIVATE void dmon__watch_recursive(const char* dirname, int fd, uint32_t m
|
||||||
closedir(dir);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DMON_ASSERT(0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
_DMON_PRIVATE void dmon__inotify_process_events(void)
|
_DMON_PRIVATE void dmon__inotify_process_events(void)
|
||||||
{
|
{
|
||||||
for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) {
|
for (int i = 0, c = stb_sb_count(_dmon.events); i < c; i++) {
|
||||||
|
@ -772,43 +789,54 @@ _DMON_PRIVATE void dmon__inotify_process_events(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove redundant modify events on a single file
|
// remove redundant modify events on a single file
|
||||||
if (ev->mask == IN_MODIFY) {
|
if (ev->mask & IN_MODIFY) {
|
||||||
for (int j = i + 1; j < c; j++) {
|
for (int j = i + 1; j < c; j++) {
|
||||||
dmon__inotify_event* check_ev = &_dmon.events[j];
|
dmon__inotify_event* check_ev = &_dmon.events[j];
|
||||||
if (check_ev->mask == IN_MODIFY && strcmp(ev->filepath, check_ev->filepath) == 0) {
|
if ((check_ev->mask & IN_MODIFY) && strcmp(ev->filepath, check_ev->filepath) == 0) {
|
||||||
ev->skip = true;
|
ev->skip = true;
|
||||||
break;
|
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 = strlen(ev->filepath);
|
||||||
|
int l2 = 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) {
|
} else if (ev->mask & IN_CREATE) {
|
||||||
bool loop_break = false;
|
bool loop_break = false;
|
||||||
for (int j = i + 1; j < c && !loop_break; j++) {
|
for (int j = i + 1; j < c && !loop_break; j++) {
|
||||||
dmon__inotify_event* check_ev = &_dmon.events[j];
|
dmon__inotify_event* check_ev = &_dmon.events[j];
|
||||||
if (check_ev->mask == IN_MOVED_FROM &&
|
if ((check_ev->mask & IN_MOVED_FROM) && strcmp(ev->filepath, check_ev->filepath) == 0) {
|
||||||
strcmp(ev->filepath, check_ev->filepath) == 0) {
|
|
||||||
// there is a case where some programs (like gedit):
|
// 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
|
// 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
|
// search for these cases and remove all of them
|
||||||
for (int k = j + 1; k < c; k++) {
|
for (int k = j + 1; k < c; k++) {
|
||||||
dmon__inotify_event* third_ev = &_dmon.events[k];
|
dmon__inotify_event* third_ev = &_dmon.events[k];
|
||||||
if (third_ev->mask == IN_MOVED_TO && check_ev->cookie == third_ev->cookie) {
|
if (third_ev->mask & IN_MOVED_TO && check_ev->cookie == third_ev->cookie) {
|
||||||
third_ev->mask = IN_MODIFY; // change to modified
|
third_ev->mask = IN_MODIFY; // change to modified
|
||||||
ev->skip = check_ev->skip = true;
|
ev->skip = check_ev->skip = true;
|
||||||
loop_break = true;
|
loop_break = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (check_ev->mask == IN_MODIFY && strcmp(ev->filepath, check_ev->filepath) == 0) {
|
} 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
|
// Another case is that file is copied. CREATE and MODIFY happens sequentially
|
||||||
// so we ignore MODIFY event
|
// so we ignore MODIFY event
|
||||||
check_ev->skip = true;
|
check_ev->skip = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (ev->mask == IN_MOVED_FROM) {
|
} else if (ev->mask & IN_MOVED_FROM) {
|
||||||
bool move_valid = false;
|
bool move_valid = false;
|
||||||
for (int j = i + 1; j < c; j++) {
|
for (int j = i + 1; j < c; j++) {
|
||||||
dmon__inotify_event* check_ev = &_dmon.events[j];
|
dmon__inotify_event* check_ev = &_dmon.events[j];
|
||||||
if (check_ev->mask == IN_MOVED_TO && ev->cookie == check_ev->cookie) {
|
if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) {
|
||||||
move_valid = true;
|
move_valid = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -820,11 +848,11 @@ _DMON_PRIVATE void dmon__inotify_process_events(void)
|
||||||
if (!move_valid) {
|
if (!move_valid) {
|
||||||
ev->mask = IN_DELETE;
|
ev->mask = IN_DELETE;
|
||||||
}
|
}
|
||||||
} else if (ev->mask == IN_MOVED_TO) {
|
} else if (ev->mask & IN_MOVED_TO) {
|
||||||
bool move_valid = false;
|
bool move_valid = false;
|
||||||
for (int j = 0; j < i; j++) {
|
for (int j = 0; j < i; j++) {
|
||||||
dmon__inotify_event* check_ev = &_dmon.events[j];
|
dmon__inotify_event* check_ev = &_dmon.events[j];
|
||||||
if (check_ev->mask == IN_MOVED_FROM && ev->cookie == check_ev->cookie) {
|
if (check_ev->mask & IN_MOVED_FROM && ev->cookie == check_ev->cookie) {
|
||||||
move_valid = true;
|
move_valid = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -851,26 +879,45 @@ _DMON_PRIVATE void dmon__inotify_process_events(void)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ev->mask) {
|
if (ev->mask & IN_CREATE) {
|
||||||
case 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
watch->watch_cb(ev->watch_id, DMON_ACTION_CREATE, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
||||||
break;
|
}
|
||||||
case IN_MODIFY:
|
else if (ev->mask & IN_MODIFY) {
|
||||||
watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
watch->watch_cb(ev->watch_id, DMON_ACTION_MODIFY, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
||||||
break;
|
}
|
||||||
case IN_MOVED_FROM: {
|
else if (ev->mask & IN_MOVED_FROM) {
|
||||||
for (int j = i + 1; j < c; j++) {
|
for (int j = i + 1; j < c; j++) {
|
||||||
dmon__inotify_event* check_ev = &_dmon.events[j];
|
dmon__inotify_event* check_ev = &_dmon.events[j];
|
||||||
if (check_ev->mask == IN_MOVED_TO && ev->cookie == check_ev->cookie) {
|
if (check_ev->mask & IN_MOVED_TO && ev->cookie == check_ev->cookie) {
|
||||||
watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir,
|
watch->watch_cb(check_ev->watch_id, DMON_ACTION_MOVE, watch->rootdir,
|
||||||
check_ev->filepath, ev->filepath, watch->user_data);
|
check_ev->filepath, ev->filepath, watch->user_data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} break;
|
}
|
||||||
case IN_DELETE:
|
else if (ev->mask & IN_DELETE) {
|
||||||
watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
watch->watch_cb(ev->watch_id, DMON_ACTION_DELETE, watch->rootdir, ev->filepath, NULL, watch->user_data);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -928,7 +975,8 @@ static void* dmon__thread(void* arg)
|
||||||
struct inotify_event* iev = (struct inotify_event*)&buff[offset];
|
struct inotify_event* iev = (struct inotify_event*)&buff[offset];
|
||||||
|
|
||||||
char filepath[DMON_MAX_PATH];
|
char filepath[DMON_MAX_PATH];
|
||||||
dmon__strcpy(filepath, sizeof(filepath), iev->name);
|
dmon__strcpy(filepath, sizeof(filepath), dmon__find_subdir(watch, iev->wd));
|
||||||
|
dmon__strcat(filepath, sizeof(filepath), iev->name);
|
||||||
|
|
||||||
// TODO: ignore directories if flag is set
|
// TODO: ignore directories if flag is set
|
||||||
|
|
||||||
|
@ -1068,7 +1116,7 @@ DMON_API_IMPL dmon_watch_id dmon_watch(const char* rootdir,
|
||||||
return dmon__make_id(0);
|
return dmon__make_id(0);
|
||||||
}
|
}
|
||||||
dmon__watch_subdir subdir;
|
dmon__watch_subdir subdir;
|
||||||
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), watch->rootdir);
|
dmon__strcpy(subdir.rootdir, sizeof(subdir.rootdir), ""); // root dir is just a dummy entry
|
||||||
stb_sb_push(watch->subdirs, subdir);
|
stb_sb_push(watch->subdirs, subdir);
|
||||||
stb_sb_push(watch->wds, wd);
|
stb_sb_push(watch->wds, wd);
|
||||||
|
|
||||||
|
@ -1379,7 +1427,7 @@ _DMON_PRIVATE void dmon__fsevent_callback(ConstFSEventStreamRef stream_ref, void
|
||||||
dmon__strcpy(abs_filepath, sizeof(abs_filepath), filepath);
|
dmon__strcpy(abs_filepath, sizeof(abs_filepath), filepath);
|
||||||
|
|
||||||
// normalize path (TODO: have to recheck this to be consistent with other platforms)
|
// normalize path (TODO: have to recheck this to be consistent with other platforms)
|
||||||
dmon__tolower(abs_filepath, sizeof(abs_filepath),
|
dmon__tolower(abs_filepath, sizeof(abs_filepath),
|
||||||
dmon__unixpath(abs_filepath, sizeof(abs_filepath), abs_filepath));
|
dmon__unixpath(abs_filepath, sizeof(abs_filepath), abs_filepath));
|
||||||
|
|
||||||
// strip the root dir
|
// strip the root dir
|
||||||
|
|
Loading…
Reference in New Issue