more code

This commit is contained in:
Florian Stinglmayr 2025-04-27 17:43:58 +02:00
parent 32d2484637
commit cf38de5355
8 changed files with 345 additions and 160 deletions

View File

@ -14,12 +14,20 @@ G_DECLARE_FINAL_TYPE(EDJournalFile, ed_journal_file, ED, JOURNALFILE, GObject);
EDJournalFile *ed_journal_file_new(void); EDJournalFile *ed_journal_file_new(void);
EDErrorCode ed_journal_file_parse_filename(
char const *basename,
gchar **date,
gint *part
);
EDErrorCode ed_journal_file_parse(EDJournalFile *file, char const *filename); EDErrorCode ed_journal_file_parse(EDJournalFile *file, char const *filename);
EDErrorCode ed_journal_file_open(EDJournalFile *file, EDErrorCode ed_journal_file_open(EDJournalFile *file,
char const *filename, char const *filename,
GError **error); GError **error);
GDateTime *ed_journal_file_get_datetime(EDJournalFile *self);
G_END_DECLS G_END_DECLS
#endif #endif

View File

@ -3,11 +3,13 @@
#include <edapi/util.h> #include <edapi/util.h>
#include <gio/gio.h> #include <gio/gio.h>
#include <stdio.h>
typedef struct { typedef struct {
gchar *filename; gchar *filename;
gchar *timestamp; gchar *datetime;
int index; GDateTime *timestamp;
gint part;
GList *entries; GList *entries;
} EDJournalFilePrivate; } EDJournalFilePrivate;
@ -35,8 +37,16 @@ static void ed_journal_file_finalize(GObject *obj)
free(p->filename); free(p->filename);
p->filename = NULL; p->filename = NULL;
free(p->timestamp); free(p->datetime);
p->timestamp = NULL; p->datetime = NULL;
if (p->timestamp != NULL) {
/* my only pet peeve of glib: you never know if their functions
* fail on a NULL pointer or not
*/
g_date_time_unref(p->timestamp);
p->timestamp = NULL;
}
g_list_free_full(p->entries, g_object_unref); g_list_free_full(p->entries, g_object_unref);
p->entries = NULL; p->entries = NULL;
@ -58,20 +68,15 @@ EDJournalFile *ed_journal_file_new(void)
return g_object_new(ED_TYPE_JOURNALFILE, NULL); return g_object_new(ED_TYPE_JOURNALFILE, NULL);
} }
EDErrorCode ed_journal_file_parse(EDJournalFile *file, char const *filename) EDErrorCode ed_journal_file_parse_filename(char const *basename,
gchar **date,
gint *part)
{ {
EDJournalFilePrivate *p = ed_journal_file_get_instance_private(file);
gchar *basename = NULL;
EDErrorCode ret = ed_error_internal;
GRegex *new_style = NULL; GRegex *new_style = NULL;
GMatchInfo *matches = NULL; GMatchInfo *matches = NULL;
gchar *timestamp = NULL; gchar *timestamp = NULL;
gchar *index = NULL; gchar *index = NULL;
EDErrorCode ret = ed_error_invalid;
return_if_true(file == NULL, ed_error_args);
return_if_true(S_EMPTY(filename), ed_error_args);
new_style = g_regex_new( new_style = g_regex_new(
"Journal\\.([\\dT\\-]+)\\.(\\d+)\\.log", "Journal\\.([\\dT\\-]+)\\.(\\d+)\\.log",
@ -79,24 +84,111 @@ EDErrorCode ed_journal_file_parse(EDJournalFile *file, char const *filename)
); );
goto_if_true(new_style == NULL, done); goto_if_true(new_style == NULL, done);
basename = g_path_get_basename(filename);
goto_if_true(S_EMPTY(basename), done);
if (g_regex_match(new_style, basename, 0, &matches)) { if (g_regex_match(new_style, basename, 0, &matches)) {
timestamp = g_match_info_fetch(matches, 1); timestamp = g_match_info_fetch(matches, 1);
index = g_match_info_fetch(matches, 2); index = g_match_info_fetch(matches, 2);
ret = ed_error_success;
} else {
ret = ed_error_invalid;
} }
g_match_info_unref(matches); g_match_info_unref(matches);
matches = NULL; matches = NULL;
goto_if_true(S_EMPTY(timestamp), done); if (ED_SUCCESS(ret)) {
goto_if_true(S_EMPTY(index), done); if (date != NULL) {
*date = timestamp;
/* we gave timestamp to the caller */
timestamp = NULL;
}
if (part != NULL) {
*part = strtol(index, NULL, 10);
}
}
g_free(p->timestamp); done:
p->timestamp = g_strdup(timestamp);
p->index = strtol(index, NULL, 0); g_free(timestamp);
g_free(index);
g_regex_unref(new_style);
return ret;
}
static EDErrorCode ed_journal_file_parse_timestamp(EDJournalFile *file)
{
EDJournalFilePrivate *p = ed_journal_file_get_instance_private(file);
return_if_true(p->datetime == NULL, ed_error_invalid);
if (strchr(p->datetime, 'T') != NULL) {
/**
* new style ISO timestamps
*/
GTimeZone *utc = g_time_zone_new_utc();
GDateTime *dt = g_date_time_new_from_iso8601(p->datetime, utc);
g_time_zone_unref(utc);
return_if_true(dt == NULL, ed_error_invalid);
p->timestamp = dt;
return ed_error_success;
} else {
/**
* old school non-ISO timestamps, used around 2021
*/
int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
int ret = 0;
ret = sscanf(p->datetime,
"%2d%2d%2d%2d%2d%2d",
&year, &month, &day,
&hour, &minute, &second
);
if (ret != 6) {
return ed_error_invalid;
}
/* add base year to years
*/
year += 2000;
GDateTime *dt = g_date_time_new_utc(
year, month, day, hour, minute, second);
return_if_true(dt == NULL, ed_error_invalid);
p->timestamp = dt;
return ed_error_success;
}
return ed_error_invalid;
}
EDErrorCode ed_journal_file_parse(EDJournalFile *self, char const *filename)
{
EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self);
gchar *basename = NULL;
EDErrorCode ret = ed_error_internal;
return_if_true(self == NULL, ed_error_args);
return_if_true(S_EMPTY(filename), ed_error_args);
basename = g_path_get_basename(filename);
goto_if_true(S_EMPTY(basename), done);
ret = ed_journal_file_parse_filename(basename, &p->datetime, &p->part);
if (ED_ERROR(ret)) {
goto done;
}
ret = ed_journal_file_parse_timestamp(self);
if (ED_ERROR(ret)) {
g_free(p->datetime);
p->datetime = NULL;
p->part = 0;
goto done;
}
g_free(p->filename); g_free(p->filename);
p->filename = g_strdup(filename); p->filename = g_strdup(filename);
@ -105,10 +197,7 @@ EDErrorCode ed_journal_file_parse(EDJournalFile *file, char const *filename)
done: done:
g_free(timestamp);
g_free(index);
g_free(basename); g_free(basename);
g_regex_unref(new_style);
return ret; return ret;
} }
@ -156,6 +245,15 @@ static EDErrorCode ed_journal_file_load(EDJournalFile *self,
} }
} }
/* ignore empty lines
*/
g_strchomp(line);
if (strlen(line) <= 0) {
g_free(line);
line = NULL;
linelen = 0;
}
entry = ed_journal_entry_new(); entry = ed_journal_entry_new();
goto_if_true(entry == NULL, done); goto_if_true(entry == NULL, done);
@ -200,3 +298,10 @@ EDErrorCode ed_journal_file_open(EDJournalFile *file,
return r; return r;
} }
GDateTime *ed_journal_file_get_datetime(EDJournalFile *self)
{
return_if_true(self == NULL, NULL);
EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self);
return p->timestamp;
}

View File

@ -1,106 +1,170 @@
#include <edapi/journal/journal.h> #include <edapi/journal/journal.h>
#include <edapi/util.h> #include <edapi/journal/file.h>
#include <edapi/util.h>
typedef struct {
gchar *location; typedef struct {
} EDJournalPrivate; gchar *location;
GList *files;
struct _EDJournal { } EDJournalPrivate;
GObject parent;
}; struct _EDJournal {
GObject parent;
struct _EDJournalClass { };
GObjectClass parent_class;
}; struct _EDJournalClass {
GObjectClass parent_class;
G_DEFINE_TYPE_EXTENDED( };
EDJournal,
ed_journal, G_DEFINE_TYPE_EXTENDED(
G_TYPE_OBJECT, EDJournal,
0, ed_journal,
G_ADD_PRIVATE(EDJournal) G_TYPE_OBJECT,
); 0,
G_ADD_PRIVATE(EDJournal)
static void ed_journal_finalize(GObject *obj) );
{
EDJournal *self = ED_JOURNAL(obj); static void ed_journal_finalize(GObject *obj)
EDJournalPrivate *p = ed_journal_get_instance_private(self); {
EDJournal *self = ED_JOURNAL(obj);
free(p->location); EDJournalPrivate *p = ed_journal_get_instance_private(self);
p->location = NULL;
free(p->location);
G_OBJECT_CLASS(ed_journal_parent_class)->finalize(obj); p->location = NULL;
}
G_OBJECT_CLASS(ed_journal_parent_class)->finalize(obj);
static void ed_journal_class_init(EDJournalClass *klass) }
{
G_OBJECT_CLASS(klass)->finalize = ed_journal_finalize; static void ed_journal_class_init(EDJournalClass *klass)
} {
G_OBJECT_CLASS(klass)->finalize = ed_journal_finalize;
static EDErrorCode ed_journal_determine_location(EDJournal *self) }
{
EDJournalPrivate *p = ed_journal_get_instance_private(self); static EDErrorCode ed_journal_determine_location(EDJournal *self)
gchar *location = NULL; {
gchar *location = NULL;
#ifdef G_OS_WIN32 gchar *tmp = NULL;
char const *env = getenv("%USERPROFILE%");
/**
if (!S_EMPTY(env)) { * on Windows we have %USERPROFILE% which points to the current
location = g_build_path( * users home directory. See if they have a Saved Games folder.
G_DIR_SEPARATOR_S, */
env, char const *env1 = getenv("%USERPROFILE%");
"Saved Games", char const *env2 = getenv("USERPROFILE");
"Frontier Developments", if (!S_EMPTY(env1) || !S_EMPTY(env2)) {
"Elite Dangerous", tmp = g_build_path(
NULL G_DIR_SEPARATOR_S,
); S_EMPTY(env1) ? env2 : env1,
} "Saved Games",
#endif "Frontier Developments",
"Elite Dangerous",
if (S_EMPTY(location)) { NULL
return ed_error_invalid; );
}
if (g_file_test(tmp, G_FILE_TEST_IS_DIR)) {
if (!g_file_test(location, G_FILE_TEST_IS_DIR)) { location = tmp;
g_free(location); tmp = NULL;
return ed_error_invalid; }
} }
g_free(p->location); if (S_EMPTY(location)) {
p->location = location; return ed_error_invalid;
}
return ed_error_success;
} if (!g_file_test(location, G_FILE_TEST_IS_DIR)) {
g_free(location);
static void ed_journal_init(EDJournal *self) return ed_error_invalid;
{ }
ed_journal_determine_location(self);
} ed_journal_set_location(self, location);
EDJournal *ed_journal_new(void) return ed_error_success;
{ }
return g_object_new(ED_TYPE_JOURNAL, NULL);
} static void ed_journal_init(EDJournal *self)
{
gchar const *ed_journal_get_location(EDJournal *self) ed_journal_determine_location(self);
{ }
return_if_true(self == NULL, NULL);
EDJournalPrivate *p = ed_journal_get_instance_private(self); EDJournal *ed_journal_new(void)
return p->location; {
} return g_object_new(ED_TYPE_JOURNAL, NULL);
}
EDErrorCode ed_journal_set_location(EDJournal *self, gchar const *dir)
{ static void ed_journal_load_files(EDJournal *self)
return_if_true(self == NULL || dir == NULL, ed_error_args); {
EDJournalPrivate *p = ed_journal_get_instance_private(self);
EDJournalPrivate *p = ed_journal_get_instance_private(self); GDir *loc = NULL;
gchar const *name = NULL;
if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) { EDErrorCode ret = ed_error_success;
return ed_error_invalid; EDJournalFile *journalfile = NULL;
}
g_list_free_full(p->files, g_object_unref);
g_free(p->location); p->files = NULL;
p->location = g_strdup(dir);
loc = g_dir_open(p->location, 0, NULL);
return ed_error_success; goto_if_true(loc == NULL, done);
}
while ((name = g_dir_read_name(loc)) != NULL) {
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
continue;
}
gchar *full = g_build_path(
G_DIR_SEPARATOR_S,
p->location,
name,
NULL
);
if (g_file_test(full, G_FILE_TEST_IS_DIR)) {
goto next;
}
ret = ed_journal_file_parse_filename(name, NULL, NULL);
goto_if_error(ret, next);
journalfile = ed_journal_file_new();
goto_if_true(journalfile == NULL, next);
ret = ed_journal_file_open(journalfile, full, NULL);
if (ED_SUCCESS(ret)) {
p->files = g_list_append(p->files, journalfile);
}
next:
g_free(full);
}
done:
if (loc != NULL) {
g_dir_close(loc);
loc = NULL;
}
}
gchar const *ed_journal_get_location(EDJournal *self)
{
return_if_true(self == NULL, NULL);
EDJournalPrivate *p = ed_journal_get_instance_private(self);
return p->location;
}
EDErrorCode ed_journal_set_location(EDJournal *self, gchar const *dir)
{
return_if_true(self == NULL || dir == NULL, ed_error_args);
EDJournalPrivate *p = ed_journal_get_instance_private(self);
if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
return ed_error_invalid;
}
g_free(p->location);
p->location = g_strdup(dir);
ed_journal_load_files(self);
return ed_error_success;
}

View File

@ -9,7 +9,7 @@ SET(TESTS
INCLUDE_DIRECTORIES( INCLUDE_DIRECTORIES(
"${CMAKE_CURRENT_SOURCE_DIR}/../include" "${CMAKE_CURRENT_SOURCE_DIR}/../include"
"${CMOCKA_INCLUDE_DIRS}" #"${CMOCKA_INCLUDE_DIRS}"
) )
FOREACH(TEST ${TESTS}) FOREACH(TEST ${TESTS})
@ -22,6 +22,6 @@ FOREACH(TEST ${TESTS})
ADD_TEST( ADD_TEST(
NAME ${TEST} NAME ${TEST}
COMMAND ${TEST} COMMAND ${TEST}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.." WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
) )
ENDFOREACH() ENDFOREACH()

View File

@ -0,0 +1 @@
{ "timestamp":"2023-04-18T04:14:56Z", "event":"Fileheader", "part":1, "language":"English/UK", "Odyssey":true, "gameversion":"4.0.0.1477", "build":"r291050/r0 " }

View File

@ -12,11 +12,13 @@ static void test_new_filename(void **state)
EDJournalFile *file = ed_journal_file_new(); EDJournalFile *file = ed_journal_file_new();
EDErrorCode ret = 0; EDErrorCode ret = 0;
GError *error = NULL;
assert_non_null(file); assert_non_null(file);
ret = ed_journal_file_parse(file, filename); ret = ed_journal_file_open(file, filename, &error);
assert_int_equal(ret, ed_error_success); assert_int_equal(ret, ed_error_success);
assert_null(error);
g_clear_object(&file); g_clear_object(&file);
} }

View File

@ -1,27 +1,32 @@
#include <stdarg.h> #define _DEFAULT_SOURCE
#include <setjmp.h> #include <stdarg.h>
#include <stddef.h> #include <setjmp.h>
#include <cmocka.h> #include <stddef.h>
#include <cmocka.h>
#include <stdio.h>
#include <edapi/journal/journal.h> #include <stdio.h>
#include <stdlib.h>
static void test_new_location(void **state)
{ #include <edapi/journal/journal.h>
EDJournal *journal = ed_journal_new();
assert_non_null(journal); static void test_userprofile_location(void **state)
{
gchar const *location = ed_journal_get_location(journal); setenv("USERPROFILE", "./", 1);
assert_non_null(location);
EDJournal *journal = ed_journal_new();
g_clear_object(&journal); assert_non_null(journal);
}
gchar const *location = ed_journal_get_location(journal);
int main(int ac, char **av) assert_non_null(location);
{
static const struct CMUnitTest tests[] = { g_clear_object(&journal);
cmocka_unit_test(test_new_location), }
};
int main(int ac, char **av)
return cmocka_run_group_tests(tests, NULL, NULL); {
} static const struct CMUnitTest tests[] = {
cmocka_unit_test(test_userprofile_location),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}