#include "twn_filewatch_c.h" #include "twn_util.h" #include "twn_engine_context_c.h" #include #include #include struct FilewatchPathToCallback { char *path; FileatchCallback callback; float tick_last_reported; enum FilewatchAction action_pending : 3; bool action_processed : 1; }; static struct FilewatchPathToCallback *filewatch_directories; static struct FilewatchPathToCallback *filewatch_files; /* note: it gets rebuilt on every addition, as api is such */ /* TODO: test whether it's possible to miss on update while watcher is being rebuilt */ /* we might have to enumerate things, just to make sure */ static x_watcher *filewatcher; static SDL_mutex *filewatcher_lock; /* TODO: check whether path and p->path match? */ static void filewatch_dispatch(XWATCHER_FILE_EVENT event, const char *path, int context, void *additional_data ) { (void)additional_data; (void)path; enum FilewatchAction action; switch (event) { case XWATCHER_FILE_CREATED: action = FILEWATCH_ACTION_FILE_CREATED; break; case XWATCHER_FILE_REMOVED: action = FILEWATCH_ACTION_FILE_DELETED; break; case XWATCHER_FILE_MODIFIED: action = FILEWATCH_ACTION_FILE_MODIFIED; break; case XWATCHER_FILE_NONE: case XWATCHER_FILE_OPENED: case XWATCHER_FILE_RENAMED: case XWATCHER_FILE_UNSPECIFIED: case XWATCHER_FILE_ATTRIBUTES_CHANGED: default: return; } SDL_LockMutex(filewatcher_lock); struct FilewatchPathToCallback *p; if (context < 0) p = &filewatch_files[-context - 1]; else p = &filewatch_directories[context]; p->tick_last_reported = ctx.game.frame_number; p->action_processed = false; p->action_pending = action; SDL_UnlockMutex(filewatcher_lock); } static bool filewatcher_rebuild(void) { if (filewatcher) xWatcher_destroy(filewatcher); filewatcher = xWatcher_create(); if (!filewatcher) { log_warn("Error creating xWatcher instance, no file watching is done"); return false; } int const filewatch_directories_len = arrlen(filewatch_directories); for (int i = 0; i < filewatch_directories_len; ++i) { xWatcher_reference ref = { .path = filewatch_directories[i].path, .callback_func = filewatch_dispatch, .context = i, }; if (!xWatcher_appendDir(filewatcher, &ref)) log_warn("Error watching dir contents: %s", filewatch_directories[i].path); } int const filewatch_files_len = arrlen(filewatch_files); for (int i = 0; i < filewatch_files_len; ++i) { xWatcher_reference ref = { .path = filewatch_files[i].path, .callback_func = filewatch_dispatch, .context = -(i + 1), /* in negative range to allow inrefence */ }; if (!xWatcher_appendDir(filewatcher, &ref)) log_warn("Error watching file: %s", filewatch_files[i].path); } if (!xWatcher_start(filewatcher)) { log_warn("Error creating xWatcher instance, no file watching is done"); xWatcher_destroy(filewatcher); filewatcher = NULL; return false; } return true; } bool filewatch_add_directory(char *dir, FileatchCallback callback) { SDL_assert(dir && callback); struct FilewatchPathToCallback const w = { .callback = callback, .path = SDL_strdup(dir), .action_processed = true, .tick_last_reported = ctx.game.frame_number, }; arrpush(filewatch_directories, w); return filewatcher_rebuild(); } bool filewatch_add_file(char *filepath, FileatchCallback callback) { SDL_assert(filepath && callback); struct FilewatchPathToCallback const f = { .callback = callback, .path = SDL_strdup(filepath), .action_processed = true, .tick_last_reported = ctx.game.frame_number, }; arrpush(filewatch_files, f); return filewatcher_rebuild(); } void filewatch_poll(void) { SDL_LockMutex(filewatcher_lock); int const filewatch_directories_len = arrlen(filewatch_directories); for (int i = 0; i < filewatch_directories_len; ++i) { if (!filewatch_directories[i].action_processed && (ctx.game.frame_number - filewatch_directories[i].tick_last_reported > FILEWATCH_MODIFIED_TICKS_MERGED)) { SDL_assert(filewatch_directories[i].action_pending != FILEWATCH_ACTION_FILE_NONE); filewatch_directories[i].callback(filewatch_directories[i].path, filewatch_directories[i].action_pending); filewatch_directories[i].action_pending = FILEWATCH_ACTION_FILE_NONE; filewatch_directories[i].action_processed = true; } } int const filewatch_files_len = arrlen(filewatch_files); for (int i = 0; i < filewatch_files_len; ++i) { if (!filewatch_files[i].action_processed && (ctx.game.frame_number - filewatch_files[i].tick_last_reported > FILEWATCH_MODIFIED_TICKS_MERGED)) { SDL_assert(filewatch_files[i].action_pending != FILEWATCH_ACTION_FILE_NONE); filewatch_files[i].callback(filewatch_files[i].path, filewatch_files[i].action_pending); filewatch_files[i].action_pending = FILEWATCH_ACTION_FILE_NONE; filewatch_files[i].action_processed = true; } } SDL_UnlockMutex(filewatcher_lock); }