From 27fcc1cea081b35ecadc6ff69873f3f72f4d6f52 Mon Sep 17 00:00:00 2001 From: Florian Stinglmayr Date: Fri, 2 May 2025 15:47:05 +0200 Subject: [PATCH] gio file monitor doesn't work in cygwin :( --- CMakeLists.txt | 1 + edwatch/CMakeLists.txt | 17 ++++ edwatch/edwatch.c | 56 ++++++++++++ lib/CMakeLists.txt | 3 + lib/include/edapi/journal/file.h | 12 ++- lib/include/edapi/journal/journal.h | 8 +- lib/src/journal/file.c | 94 +++++++++++++++++-- lib/src/journal/journal.c | 137 ++++++++++++++++++++++++---- lib/tests/test-journal-file.c | 16 ++-- lib/tests/test-journal.c | 4 +- 10 files changed, 312 insertions(+), 36 deletions(-) create mode 100644 edwatch/CMakeLists.txt create mode 100644 edwatch/edwatch.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7723c9d..5110425 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,3 +5,4 @@ ADD_DEFINITIONS("-Wall -Werror -std=c99") ENABLE_TESTING() ADD_SUBDIRECTORY(lib) +ADD_SUBDIRECTORY(edwatch) diff --git a/edwatch/CMakeLists.txt b/edwatch/CMakeLists.txt new file mode 100644 index 0000000..f1d9774 --- /dev/null +++ b/edwatch/CMakeLists.txt @@ -0,0 +1,17 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.24) +PROJECT(edterm) + +SET(SOURCES + "edwatch.c" +) + +INCLUDE_DIRECTORIES( + ${GLIB2_INCLUDE_DIRS} + ${EDAPI_INCLUDE_DIRS} +) + +ADD_EXECUTABLE("edwatch" ${SOURCES}) +TARGET_LINK_LIBRARIES( + "edwatch" + "edapi" + ) diff --git a/edwatch/edwatch.c b/edwatch/edwatch.c new file mode 100644 index 0000000..649e397 --- /dev/null +++ b/edwatch/edwatch.c @@ -0,0 +1,56 @@ +#include + +#include + +#include +#include +#include + +static EDJournal *journal = NULL; +static GApplication *app = NULL; + +static void interrupt(int signal) +{ + if (app != NULL) { + g_application_release(app); + } +} + +static void new_entry(EDJournal *sender, EDJournalFile *file, + EDJournalEntry *entry, gpointer data) +{ + fprintf(stdout, "new event!\n"); +} + +static void activate(GApplication *self, gpointer data) +{ + gchar *location = NULL; + GError *error = NULL; + + journal = ed_journal_new_with_location(location, &error); + if (journal == NULL) { + fprintf(stderr, "failed to open journal: %s\n", + error->message); + g_application_quit(self); + return; + } + + g_signal_connect(journal, "new-entry", G_CALLBACK(new_entry), NULL); + + g_application_hold(self); +} + +int main(int ac, char **av) +{ + app = g_application_new("org.nola.edwatch", 0); + if (app == NULL) { + return 3; + } + + signal(SIGINT, interrupt); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + + g_application_run(app, 0, NULL); + + return 0; +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d705890..33981cd 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -18,6 +18,9 @@ SET(SOURCES "src/journal/journal.c" ) +SET(EDAPI_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" + CACHE STRING "EDAPI lib include dir") + INCLUDE_DIRECTORIES( "include" ${GIO2_INCLUDE_DIRS} diff --git a/lib/include/edapi/journal/file.h b/lib/include/edapi/journal/file.h index 557f62e..c05ee02 100644 --- a/lib/include/edapi/journal/file.h +++ b/lib/include/edapi/journal/file.h @@ -33,10 +33,20 @@ EDErrorCode ed_journal_file_parse_filename( * * To fully load all entries call ed_journal_file_load(). */ -EDErrorCode ed_journal_file_open(EDJournalFile *file, +EDErrorCode ed_journal_file_peek(EDJournalFile *file, char const *filename, GError **error); +/** + * Opens the file and seeks to its end. If any new entries are + * added by the game then ed_journal_file_read() will return new + * entries. + */ +EDErrorCode ed_journal_file_open_tail(EDJournalFile *self); +void ed_journal_file_close(EDJournalFile *self); + +EDJournalEntry *ed_journal_file_read(EDJournalFile *self, GError **error); + /** * Load all entries from this given journal. */ diff --git a/lib/include/edapi/journal/journal.h b/lib/include/edapi/journal/journal.h index 68ee2f3..b2538fd 100644 --- a/lib/include/edapi/journal/journal.h +++ b/lib/include/edapi/journal/journal.h @@ -13,12 +13,16 @@ G_DECLARE_FINAL_TYPE( #define ED_TYPE_JOURNAL ed_journal_get_type() +gchar *ed_journal_determine_location(void); + EDJournal *ed_journal_new(void); -EDJournal *ed_journal_new_with_location(gchar const *dir); +EDJournal *ed_journal_new_with_location(gchar const *dir, GError **error); gchar const *ed_journal_get_location(EDJournal *j); -EDErrorCode ed_journal_set_location(EDJournal *j, gchar const *dir); + +void ed_journal_close(EDJournal *self); +EDErrorCode ed_journal_open(EDJournal *j, gchar const *dir, GError **error); /** * Get a list of files. Files are sorted in ascending order, with diff --git a/lib/src/journal/file.c b/lib/src/journal/file.c index 9cc0c83..f69ca7c 100644 --- a/lib/src/journal/file.c +++ b/lib/src/journal/file.c @@ -8,6 +8,7 @@ typedef struct { gchar *filename; + GFile *file; gchar *datetime; GDateTime *timestamp; gint part; @@ -15,6 +16,9 @@ typedef struct { EDCommander *commander; gchar *gameversion; + GDataInputStream *reader; + GFileInputStream *stream; + EDJournalEntry *first; EDJournalEntry *last; } EDJournalFilePrivate; @@ -40,8 +44,13 @@ static void ed_journal_file_dispose(GObject *obj) EDJournalFile *self = ED_JOURNALFILE(obj); EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self); + g_clear_object(&p->file); + g_clear_object(&p->reader); + g_clear_object(&p->stream); + g_clear_object(&p->first); g_clear_object(&p->last); + g_clear_object(&p->commander); G_OBJECT_CLASS(ed_journal_file_parent_class)->dispose(obj); @@ -215,6 +224,7 @@ static EDErrorCode ed_journal_file_parse_(EDJournalFile *self, g_free(p->filename); p->filename = g_strdup(filename); + p->file = g_file_new_for_path(p->filename); ret = ed_error_success; @@ -257,7 +267,6 @@ static EDErrorCode ed_journal_file_read_first_(EDJournalFile *self) EDErrorCode ret = ed_error_internal; EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self); - GFile *file = NULL; GFileInputStream *stream = NULL; GDataInputStream *reader = NULL; EDJournalEntry *entry = NULL; @@ -265,10 +274,7 @@ static EDErrorCode ed_journal_file_read_first_(EDJournalFile *self) char *line = NULL; gsize linelen = 0; - file = g_file_new_for_path(p->filename); - goto_if_true(file == NULL, done); - - stream = g_file_read(file, NULL, NULL); + stream = g_file_read(p->file, NULL, NULL); if (stream == NULL) { goto done; } @@ -319,7 +325,6 @@ done: g_clear_object(&reader); g_clear_object(&stream); - g_clear_object(&file); return ret; } @@ -524,7 +529,7 @@ done: return ret; } -EDErrorCode ed_journal_file_open(EDJournalFile *file, +EDErrorCode ed_journal_file_peek(EDJournalFile *file, char const *filename, GError **error) { @@ -640,3 +645,78 @@ gboolean ed_journal_file_has_entries_in_range(EDJournalFile *self, return FALSE; } + +EDJournalEntry *ed_journal_file_read(EDJournalFile *self, GError **error) +{ + return_if_true(self == NULL, NULL); + EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self); + GError *tmp = NULL; + + if (p->reader == NULL) { + return NULL; + } + + gchar *line = NULL; + gsize linelen = 0; + + line = g_data_input_stream_read_line_utf8( + p->reader, &linelen, NULL, &tmp); + if (line == NULL) { + g_propagate_error(error, tmp); + return NULL; + } + + g_strchomp(line); + if (strlen(line) <= 0) { + g_free(line); + return NULL; + } + + EDJournalEntry *entry = ed_journal_entry_new_parse(line, &tmp); + g_free(line); + + if (entry == NULL) { + g_propagate_error(error, tmp); + return NULL; + } + + return entry; +} + +void ed_journal_file_close(EDJournalFile *self) +{ + return_if_true(self == NULL,); + EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self); + + g_clear_object(&p->stream); + g_clear_object(&p->reader); +} + +EDErrorCode ed_journal_file_open_tail(EDJournalFile *self) +{ + return_if_true(self == NULL, ed_error_args); + EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self); + + EDErrorCode ret = ed_error_internal; + + p->stream = g_file_read(p->file, NULL, NULL); + goto_if_true(p->stream == NULL, done); + + p->reader = g_data_input_stream_new(G_INPUT_STREAM(p->stream)); + goto_if_true(p->reader == NULL, done); + + if (!g_seekable_seek(G_SEEKABLE(p->stream), 0, G_SEEK_END, NULL, NULL)) { + goto done; + } + + ret = ed_error_success; + +done: + + if (ED_ERROR(ret)) { + g_clear_object(&p->stream); + g_clear_object(&p->reader); + } + + return ret; +} diff --git a/lib/src/journal/journal.c b/lib/src/journal/journal.c index b1d0ac6..1404797 100644 --- a/lib/src/journal/journal.c +++ b/lib/src/journal/journal.c @@ -2,9 +2,14 @@ #include #include +#include +#include + typedef struct { gchar *location; + GFile *loc; GList *files; + GFileMonitor *monitor; } EDJournalPrivate; struct _EDJournal { @@ -23,6 +28,10 @@ G_DEFINE_TYPE_EXTENDED( G_ADD_PRIVATE(EDJournal) ); +static void ed_journal_changed(GFileMonitor *self, GFile *file, GFile *other, + GFileMonitorEvent event, + gpointer user); + static void ed_journal_finalize(GObject *obj) { EDJournal *self = ED_JOURNAL(obj); @@ -34,12 +43,34 @@ static void ed_journal_finalize(GObject *obj) G_OBJECT_CLASS(ed_journal_parent_class)->finalize(obj); } -static void ed_journal_class_init(EDJournalClass *klass) +static void ed_journal_dispose(GObject *obj) { - G_OBJECT_CLASS(klass)->finalize = ed_journal_finalize; + EDJournal *self = ED_JOURNAL(obj); + EDJournalPrivate *p = ed_journal_get_instance_private(self); + + g_clear_object(&p->loc); + g_clear_object(&p->monitor); + + G_OBJECT_CLASS(ed_journal_parent_class)->dispose(obj); } -static EDErrorCode ed_journal_determine_location(EDJournal *self) +static void ed_journal_class_init(EDJournalClass *klass) +{ + g_signal_new("new-entry", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 2, + ED_TYPE_JOURNALFILE, + ED_TYPE_JOURNALENTRY + ); + + G_OBJECT_CLASS(klass)->finalize = ed_journal_finalize; + G_OBJECT_CLASS(klass)->dispose = ed_journal_dispose; +} + +gchar *ed_journal_determine_location(void) { gchar *location = NULL; gchar *tmp = NULL; @@ -67,22 +98,19 @@ static EDErrorCode ed_journal_determine_location(EDJournal *self) } if (S_EMPTY(location)) { - return ed_error_invalid; + return NULL; } if (!g_file_test(location, G_FILE_TEST_IS_DIR)) { g_free(location); - return ed_error_invalid; + return NULL; } - ed_journal_set_location(self, location); - - return ed_error_success; + return location; } static void ed_journal_init(EDJournal *self) { - ed_journal_determine_location(self); } EDJournal *ed_journal_new(void) @@ -90,18 +118,32 @@ EDJournal *ed_journal_new(void) return g_object_new(ED_TYPE_JOURNAL, NULL); } -EDJournal *ed_journal_new_with_location(gchar const *dir) +EDJournal *ed_journal_new_with_location(gchar const *dir, GError **error) { EDJournal *j = NULL; EDErrorCode ret = ed_error_success; + GError *tmp = NULL; + + if (dir == NULL) { + gchar *loc = ed_journal_determine_location(); + return_if_true(loc == NULL, NULL); + + j = ed_journal_new_with_location(loc, error); + g_free(loc); + + return j; + } j = ed_journal_new(); return_if_true(j == NULL, NULL); - ret = ed_journal_set_location(j, dir); - if (ED_ERROR(ret)) { - g_clear_object(&j); - return NULL; + if (!S_EMPTY(dir)) { + ret = ed_journal_open(j, dir, &tmp); + if (ED_ERROR(ret)) { + g_propagate_error(error, tmp); + g_clear_object(&j); + return NULL; + } } return j; @@ -143,7 +185,7 @@ static void ed_journal_load_files(EDJournal *self) journalfile = ed_journal_file_new(); goto_if_true(journalfile == NULL, next); - ret = ed_journal_file_open(journalfile, full, NULL); + ret = ed_journal_file_peek(journalfile, full, NULL); if (ED_SUCCESS(ret)) { p->files = g_list_append(p->files, journalfile); } @@ -164,6 +206,40 @@ done: } } +static EDErrorCode ed_journal_watch(EDJournal *self, GError **error) +{ + return_if_true(self == NULL, ed_error_args); + + EDJournalPrivate *p = ed_journal_get_instance_private(self); + GError *tmp = NULL; + + g_clear_object(&p->monitor); + + p->monitor = g_file_monitor_directory(p->loc, 0, NULL, &tmp); + if (p->monitor == NULL) { + g_propagate_error(error, tmp); + return ed_error_internal; + } + + g_signal_connect( + p->monitor, "changed", G_CALLBACK(ed_journal_changed), self); + + return ed_error_success; +} + +static void ed_journal_changed(GFileMonitor *mon, GFile *file, GFile *other, + GFileMonitorEvent event, + gpointer user) +{ + EDJournal *self = ED_JOURNAL(user); + + if (event == G_FILE_MONITOR_EVENT_CHANGED) { + printf("file changed!\n"); + } + + (void)self; +} + gchar const *ed_journal_get_location(EDJournal *self) { return_if_true(self == NULL, NULL); @@ -171,11 +247,30 @@ gchar const *ed_journal_get_location(EDJournal *self) return p->location; } -EDErrorCode ed_journal_set_location(EDJournal *self, gchar const *dir) +void ed_journal_close(EDJournal *self) +{ + return_if_true(self == NULL,); + + EDJournalPrivate *p = ed_journal_get_instance_private(self); + + g_free(p->location); + p->location = NULL; + + g_clear_object(&p->loc); + + g_clear_object(&p->monitor); + + g_list_free_full(p->files, g_object_unref); + p->files = NULL; +} + +EDErrorCode ed_journal_open(EDJournal *self, gchar const *dir, GError **error) { return_if_true(self == NULL || dir == NULL, ed_error_args); EDJournalPrivate *p = ed_journal_get_instance_private(self); + EDErrorCode ret = 0; + GError *tmp = NULL; if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) { return ed_error_invalid; @@ -184,8 +279,18 @@ EDErrorCode ed_journal_set_location(EDJournal *self, gchar const *dir) g_free(p->location); p->location = g_strdup(dir); + g_clear_object(&p->loc); + p->loc = g_file_new_for_path(dir); + ed_journal_load_files(self); + ret = ed_journal_watch(self, &tmp); + if (ED_ERROR(ret)) { + g_propagate_error(error, tmp); + ed_journal_close(self); + return ed_error_internal; + } + return ed_error_success; } diff --git a/lib/tests/test-journal-file.c b/lib/tests/test-journal-file.c index ead51a0..0afa766 100644 --- a/lib/tests/test-journal-file.c +++ b/lib/tests/test-journal-file.c @@ -16,7 +16,7 @@ static void test_new_filename(void **state) assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_int_equal(ret, ed_error_success); assert_null(error); @@ -33,7 +33,7 @@ static void test_old_filename(void **state) assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_int_equal(ret, ed_error_success); assert_null(error); @@ -50,7 +50,7 @@ static void test_new_datetime(void **state) assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_int_equal(ret, ed_error_success); assert_null(error); @@ -79,7 +79,7 @@ static void test_old_datetime(void **state) assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_int_equal(ret, ed_error_success); assert_null(error); @@ -109,7 +109,7 @@ static void test_valid_load(void **state) file = ed_journal_file_new(); assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_null(error); assert_int_equal(ret, ed_error_success); @@ -128,7 +128,7 @@ static void test_valid_peek(void **state) file = ed_journal_file_new(); assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_null(error); assert_int_equal(ret, ed_error_success); @@ -161,7 +161,7 @@ static void test_first_last(void **state) file = ed_journal_file_new(); assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_null(error); assert_int_equal(ret, ed_error_success); @@ -193,7 +193,7 @@ static void test_in_range(void **state) file = ed_journal_file_new(); assert_non_null(file); - ret = ed_journal_file_open(file, filename, &error); + ret = ed_journal_file_peek(file, filename, &error); assert_null(error); assert_int_equal(ret, ed_error_success); diff --git a/lib/tests/test-journal.c b/lib/tests/test-journal.c index 1a60fdd..8f8814f 100644 --- a/lib/tests/test-journal.c +++ b/lib/tests/test-journal.c @@ -14,7 +14,7 @@ static void test_userprofile_location(void **state) { setenv("USERPROFILE", "./", 1); - EDJournal *journal = ed_journal_new(); + EDJournal *journal = ed_journal_new_with_location(NULL, NULL); assert_non_null(journal); gchar const *location = ed_journal_get_location(journal); @@ -27,7 +27,7 @@ static void test_file_sorting(void **state) { setenv("USERPROFILE", "./", 1); - EDJournal *journal = ed_journal_new(); + EDJournal *journal = ed_journal_new_with_location(NULL, NULL); assert_non_null(journal); gchar const *location = ed_journal_get_location(journal);