filewatch: add filewatch module for basic file monitoring
Currently only an inotify-based backend is implemented.
This commit is contained in:
parent
b39c9ba78e
commit
e629d927ba
7 changed files with 312 additions and 0 deletions
|
@ -47,6 +47,8 @@ typedef enum {
|
|||
|
||||
TE_AUDIO_BGM_STARTED,
|
||||
|
||||
TE_FILEWATCH,
|
||||
|
||||
NUM_TAISEI_EVENTS
|
||||
} TaiseiEvent;
|
||||
|
||||
|
|
23
src/filewatch/filewatch.h
Normal file
23
src/filewatch/filewatch.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT License.
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "taisei.h"
|
||||
|
||||
typedef enum FileWatchEvent {
|
||||
FILEWATCH_FILE_UPDATED,
|
||||
FILEWATCH_FILE_DELETED,
|
||||
} FileWatchEvent;
|
||||
|
||||
typedef struct FileWatch FileWatch;
|
||||
|
||||
void filewatch_init(void);
|
||||
void filewatch_shutdown(void);
|
||||
|
||||
FileWatch *filewatch_watch(const char *syspath);
|
||||
void filewatch_unwatch(FileWatch *watch);
|
230
src/filewatch/filewatch_inotify.c
Normal file
230
src/filewatch/filewatch_inotify.c
Normal file
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT License.
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "filewatch.h"
|
||||
#include "util.h"
|
||||
#include "events.h"
|
||||
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define INVALID_WD (-1)
|
||||
|
||||
#define WATCHED_EVENTS ( \
|
||||
IN_ATTRIB | \
|
||||
IN_CLOSE_WRITE | \
|
||||
IN_DELETE_SELF | \
|
||||
IN_MODIFY | \
|
||||
IN_MOVE_SELF | \
|
||||
0)
|
||||
|
||||
#define DELETION_EVENTS ( \
|
||||
IN_ATTRIB | /* could be an inode refcount update… */ \
|
||||
IN_DELETE_SELF | \
|
||||
IN_IGNORED | \
|
||||
IN_MOVE_SELF | \
|
||||
0)
|
||||
|
||||
#define FW (*_fw_globals)
|
||||
|
||||
#define EVENTS_BUF_SIZE 4096
|
||||
|
||||
struct FileWatch {
|
||||
int wd;
|
||||
SDL_atomic_t refs;
|
||||
bool updated;
|
||||
bool deleted;
|
||||
};
|
||||
|
||||
struct {
|
||||
int inotify;
|
||||
ht_int2ptr_ts_t wd_to_watch;
|
||||
SDL_mutex *modify_mtx;
|
||||
DYNAMIC_ARRAY(FileWatch*) deactivate_list;
|
||||
} *_fw_globals;
|
||||
|
||||
static bool filewatch_frame_event(SDL_Event *e, void *a);
|
||||
|
||||
void filewatch_init(void) {
|
||||
assert(_fw_globals == NULL);
|
||||
|
||||
int inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
|
||||
if(UNLIKELY(inotify < 0)) {
|
||||
log_error("Failed to initialize inotify: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
_fw_globals = calloc(1, sizeof(*_fw_globals));
|
||||
FW.inotify = inotify;
|
||||
ht_create(&FW.wd_to_watch);
|
||||
|
||||
FW.modify_mtx = SDL_CreateMutex();
|
||||
|
||||
if(UNLIKELY(FW.modify_mtx == NULL)) {
|
||||
log_sdl_error(LOG_WARN, "SDL_CreateMutex");
|
||||
}
|
||||
|
||||
events_register_handler(&(EventHandler) {
|
||||
.proc = filewatch_frame_event,
|
||||
.priority = EPRIO_SYSTEM,
|
||||
.event_type = MAKE_TAISEI_EVENT(TE_FRAME),
|
||||
});
|
||||
}
|
||||
|
||||
void filewatch_shutdown(void) {
|
||||
if(_fw_globals) {
|
||||
events_unregister_handler(filewatch_frame_event);
|
||||
|
||||
close(FW.inotify);
|
||||
ht_destroy(&FW.wd_to_watch);
|
||||
dynarray_free_data(&FW.deactivate_list);
|
||||
SDL_DestroyMutex(FW.modify_mtx);
|
||||
|
||||
free(_fw_globals);
|
||||
_fw_globals = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
FileWatch *filewatch_watch(const char *syspath) {
|
||||
if(!&FW) {
|
||||
log_error("Subsystem not initialized");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_LockMutex(FW.modify_mtx);
|
||||
int wd = inotify_add_watch(FW.inotify, syspath, WATCHED_EVENTS);
|
||||
|
||||
if(UNLIKELY(wd == INVALID_WD)) {
|
||||
SDL_UnlockMutex(FW.modify_mtx);
|
||||
log_error("Failed to watch '%s': %s", syspath, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FileWatch *w;
|
||||
|
||||
if(ht_lookup(&FW.wd_to_watch, wd, (void**)&w)) {
|
||||
assert(w->wd == wd);
|
||||
} else {
|
||||
w = calloc(1, sizeof(*w));
|
||||
w->wd = wd;
|
||||
ht_set(&FW.wd_to_watch, wd, w);
|
||||
}
|
||||
|
||||
SDL_AtomicIncRef(&w->refs);
|
||||
SDL_UnlockMutex(FW.modify_mtx);
|
||||
|
||||
log_debug("Watching '%s'; wd = %i", syspath, w->wd);
|
||||
return NOT_NULL(w);
|
||||
}
|
||||
|
||||
static void filewatch_deactivate(FileWatch *watch) {
|
||||
SDL_LockMutex(FW.modify_mtx);
|
||||
|
||||
if(watch->wd != INVALID_WD) {
|
||||
assert(ht_get(&FW.wd_to_watch, watch->wd, NULL) == watch);
|
||||
|
||||
if(UNLIKELY(inotify_rm_watch(FW.inotify, watch->wd) == -1)) {
|
||||
log_warn("Failed to remove inotify watch: %s", strerror(errno));
|
||||
}
|
||||
|
||||
ht_unset(&FW.wd_to_watch, watch->wd);
|
||||
watch->wd = INVALID_WD;
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(FW.modify_mtx);
|
||||
}
|
||||
|
||||
void filewatch_unwatch(FileWatch *watch) {
|
||||
if(SDL_AtomicDecRef(&watch->refs)) {
|
||||
filewatch_deactivate(watch);
|
||||
free(watch);
|
||||
}
|
||||
}
|
||||
|
||||
static void filewatch_process_events(ssize_t bufsize, char buf[bufsize]) {
|
||||
struct inotify_event *e;
|
||||
|
||||
for(ssize_t i = 0; i < bufsize;) {
|
||||
e = CASTPTR_ASSUME_ALIGNED(buf + i, struct inotify_event);
|
||||
FileWatch *w = ht_get(&FW.wd_to_watch, e->wd, NULL);
|
||||
|
||||
if(w == NULL) {
|
||||
log_debug("inotify event wd=%i mask=0x%08x (ignore)", e->wd, e->mask);
|
||||
goto skip;
|
||||
}
|
||||
|
||||
if(e->mask & DELETION_EVENTS) {
|
||||
log_debug("inotify event wd=%i mask=0x%08x (delete)", e->wd, e->mask);
|
||||
w->deleted = true;
|
||||
} else {
|
||||
log_debug("inotify event wd=%i mask=0x%08x (update)", e->wd, e->mask);
|
||||
w->updated = true;
|
||||
}
|
||||
skip:
|
||||
i += sizeof(*e) + e->len;
|
||||
}
|
||||
}
|
||||
|
||||
static void filewatch_process(void) {
|
||||
SDL_LockMutex(FW.modify_mtx);
|
||||
alignas(alignof(struct inotify_event)) char buf[EVENTS_BUF_SIZE];
|
||||
|
||||
for(;;) {
|
||||
ssize_t r = read(FW.inotify, buf, sizeof(buf));
|
||||
|
||||
if(r == -1) {
|
||||
if(errno != EAGAIN) {
|
||||
log_error("%s", strerror(errno));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
filewatch_process_events(r, buf);
|
||||
}
|
||||
|
||||
ht_lock(&FW.wd_to_watch);
|
||||
ht_int2ptr_ts_iter_t iter;
|
||||
ht_iter_begin(&FW.wd_to_watch, &iter);
|
||||
|
||||
for(;iter.has_data; ht_iter_next(&iter)) {
|
||||
FileWatch *w = NOT_NULL(iter.value);
|
||||
|
||||
if(w->deleted) {
|
||||
log_debug("Emitting delete event for wd %i", w->wd);
|
||||
events_emit(TE_FILEWATCH, FILEWATCH_FILE_DELETED, w, NULL);
|
||||
w->deleted = w->updated = false;
|
||||
// defer filewatch_deactivate(w) because it modifies this hashtable
|
||||
*dynarray_append(&FW.deactivate_list) = w;
|
||||
} else if(w->updated) {
|
||||
log_debug("Emitting update event for wd %i", w->wd);
|
||||
events_emit(TE_FILEWATCH, FILEWATCH_FILE_UPDATED, w, NULL);
|
||||
w->updated = false;
|
||||
}
|
||||
}
|
||||
|
||||
ht_iter_end(&iter);
|
||||
ht_unlock(&FW.wd_to_watch);
|
||||
|
||||
dynarray_foreach_elem(&FW.deactivate_list, FileWatch **w, {
|
||||
filewatch_deactivate(*w);
|
||||
});
|
||||
|
||||
FW.deactivate_list.num_elements = 0;
|
||||
SDL_UnlockMutex(FW.modify_mtx);
|
||||
}
|
||||
|
||||
static bool filewatch_frame_event(SDL_Event *e, void *a) {
|
||||
filewatch_process();
|
||||
return false;
|
||||
}
|
24
src/filewatch/filewatch_null.c
Normal file
24
src/filewatch/filewatch_null.c
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* This software is licensed under the terms of the MIT License.
|
||||
* See COPYING for further information.
|
||||
* ---
|
||||
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
||||
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
||||
*/
|
||||
|
||||
#include "taisei.h"
|
||||
|
||||
#include "filewatch.h"
|
||||
|
||||
void filewatch_init(void) {
|
||||
}
|
||||
|
||||
void filewatch_shutdown(void) {
|
||||
}
|
||||
|
||||
FileWatch *filewatch_watch(const char *syspath) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void filewatch_unwatch(FileWatch *watch) {
|
||||
}
|
28
src/filewatch/meson.build
Normal file
28
src/filewatch/meson.build
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
filewatch_enabled = true
|
||||
filewatch_src = []
|
||||
|
||||
if filewatch_enabled
|
||||
# For shims like inotify-kqueue
|
||||
inotify_dep = dependency('libinotify', required : false)
|
||||
if not inotify_dep.found()
|
||||
inotify_dep = cc.find_library('inotify', required : false)
|
||||
endif
|
||||
|
||||
filewatch_enabled = (
|
||||
have_posix and
|
||||
cc.has_function('inotify_init1', dependencies : inotify_dep) and
|
||||
cc.has_header_symbol('sys/inotify.h', 'struct inotify_event', dependencies : inotify_dep)
|
||||
)
|
||||
|
||||
if not filewatch_enabled
|
||||
warning('No inotify support; live file monitoring not available')
|
||||
endif
|
||||
endif
|
||||
|
||||
if filewatch_enabled
|
||||
taisei_deps += inotify_dep
|
||||
filewatch_src += files('filewatch_inotify.c')
|
||||
else
|
||||
filewatch_src += files('filewatch_null.c')
|
||||
endif
|
|
@ -30,6 +30,7 @@
|
|||
#include "util/gamemode.h"
|
||||
#include "cutscenes/cutscene.h"
|
||||
#include "replay/struct.h"
|
||||
#include "filewatch/filewatch.h"
|
||||
|
||||
attr_unused
|
||||
static void taisei_shutdown(void) {
|
||||
|
@ -45,6 +46,7 @@ static void taisei_shutdown(void) {
|
|||
gamemode_shutdown();
|
||||
free_all_refs();
|
||||
free_resources(true);
|
||||
filewatch_shutdown();
|
||||
taskmgr_global_shutdown();
|
||||
audio_shutdown();
|
||||
video_shutdown();
|
||||
|
@ -337,6 +339,7 @@ static void main_post_vfsinit(CallChainResult ccr) {
|
|||
init_global(&ctx->cli);
|
||||
events_init();
|
||||
video_init();
|
||||
filewatch_init();
|
||||
init_resources();
|
||||
r_post_init();
|
||||
draw_loading_screen();
|
||||
|
|
|
@ -127,6 +127,7 @@ subdir('audio')
|
|||
subdir('cutscenes')
|
||||
subdir('dialog')
|
||||
subdir('eventloop')
|
||||
subdir('filewatch')
|
||||
subdir('lasers')
|
||||
subdir('menu')
|
||||
subdir('pixmap')
|
||||
|
@ -146,6 +147,7 @@ taisei_src += [
|
|||
cutscenes_src,
|
||||
dialog_src,
|
||||
eventloop_src,
|
||||
filewatch_src,
|
||||
lasers_src,
|
||||
menu_src,
|
||||
pixmap_src,
|
||||
|
|
Loading…
Reference in a new issue