add support for first last
This commit is contained in:
parent
c314f65feb
commit
9d619c44f3
@ -1,6 +1,7 @@
|
||||
#ifndef EDAPI_JOURNAL_FILE_H
|
||||
#define EDAPI_JOURNAL_FILE_H
|
||||
|
||||
#include <edapi/journal/entry.h>
|
||||
#include <edapi/error.h>
|
||||
|
||||
#include <glib.h>
|
||||
@ -25,7 +26,11 @@ EDErrorCode ed_journal_file_parse_filename(
|
||||
* function fails if the given file cannot be opened. Loading
|
||||
* entries is a time consuming task (especially with multiple
|
||||
* files in a journal), so this function only peeks the first
|
||||
* few entries to figure out game version and CMDR name.
|
||||
* few entries to figure out game version and CMDR name, and
|
||||
* it peeks the last entry to determine date range of the
|
||||
* journal file.
|
||||
*
|
||||
* To fully load all entries call ed_journal_file_load().
|
||||
*/
|
||||
EDErrorCode ed_journal_file_open(EDJournalFile *file,
|
||||
char const *filename,
|
||||
@ -42,6 +47,9 @@ gchar const *ed_journal_file_get_commander(EDJournalFile *self);
|
||||
|
||||
gchar const *ed_journal_file_get_gameversion(EDJournalFile *self);
|
||||
|
||||
EDJournalEntry *ed_journal_file_get_first(EDJournalFile *self);
|
||||
EDJournalEntry *ed_journal_file_get_last(EDJournalFile *self);
|
||||
|
||||
gint ed_journal_file_compare(EDJournalFile *lhs, EDJournalFile *rhs);
|
||||
|
||||
G_END_DECLS
|
||||
|
@ -13,6 +13,9 @@ typedef struct {
|
||||
GList *entries;
|
||||
gchar *commander;
|
||||
gchar *gameversion;
|
||||
|
||||
EDJournalEntry *first;
|
||||
EDJournalEntry *last;
|
||||
} EDJournalFilePrivate;
|
||||
|
||||
struct _EDJournalFile {
|
||||
@ -31,6 +34,17 @@ G_DEFINE_TYPE_EXTENDED(
|
||||
G_ADD_PRIVATE(EDJournalFile)
|
||||
);
|
||||
|
||||
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->first);
|
||||
g_clear_object(&p->last);
|
||||
|
||||
G_OBJECT_CLASS(ed_journal_file_parent_class)->dispose(obj);
|
||||
}
|
||||
|
||||
static void ed_journal_file_finalize(GObject *obj)
|
||||
{
|
||||
EDJournalFile *self = ED_JOURNALFILE(obj);
|
||||
@ -64,6 +78,7 @@ static void ed_journal_file_finalize(GObject *obj)
|
||||
|
||||
static void ed_journal_file_class_init(EDJournalFileClass *klass)
|
||||
{
|
||||
G_OBJECT_CLASS(klass)->dispose = ed_journal_file_dispose;
|
||||
G_OBJECT_CLASS(klass)->finalize = ed_journal_file_finalize;
|
||||
}
|
||||
|
||||
@ -244,6 +259,183 @@ ed_journal_file_parse_commander(EDJournalFile *self,
|
||||
return ed_error_success;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
if (stream == NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
reader = g_data_input_stream_new(G_INPUT_STREAM(stream));
|
||||
if (reader == NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
while (TRUE) {
|
||||
line = g_data_input_stream_read_line_utf8(
|
||||
reader, &linelen, NULL, NULL
|
||||
);
|
||||
goto_if_true(line == NULL, done);
|
||||
|
||||
/* ignore empty lines
|
||||
*/
|
||||
g_strchomp(line);
|
||||
if (strlen(line) <= 0) {
|
||||
g_free(line);
|
||||
line = NULL;
|
||||
linelen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
entry = ed_journal_entry_new_parse(line, NULL);
|
||||
|
||||
g_free(line);
|
||||
line = NULL;
|
||||
linelen = 0;
|
||||
|
||||
if (entry == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
g_clear_object(&p->first);
|
||||
p->first = g_object_ref(entry);
|
||||
|
||||
g_clear_object(&entry);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
ret = ed_error_success;
|
||||
|
||||
done:
|
||||
|
||||
g_clear_object(&reader);
|
||||
g_clear_object(&stream);
|
||||
g_clear_object(&file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static EDErrorCode ed_journal_file_read_last_(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;
|
||||
|
||||
goffset end = 0, pos = 0;
|
||||
|
||||
char *line = NULL;
|
||||
char *last = 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);
|
||||
if (stream == NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
reader = g_data_input_stream_new(G_INPUT_STREAM(stream));
|
||||
if (reader == NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!g_seekable_seek(G_SEEKABLE(stream), 0, G_SEEK_END, NULL, NULL)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
end = g_seekable_tell(G_SEEKABLE(stream));
|
||||
pos = end;
|
||||
|
||||
while (TRUE) {
|
||||
/* arbitrary */
|
||||
pos = end - 100;
|
||||
if (pos <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!g_seekable_seek(G_SEEKABLE(stream), pos, G_SEEK_SET,
|
||||
NULL, NULL)) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* try to read last line if we accidentally seeked over two
|
||||
*/
|
||||
do {
|
||||
line = g_data_input_stream_read_line_utf8(
|
||||
reader, &linelen, NULL, NULL
|
||||
);
|
||||
|
||||
if (line == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* ignore empty lines
|
||||
*/
|
||||
g_strchomp(line);
|
||||
if (strlen(line) <= 0) {
|
||||
g_free(line);
|
||||
line = NULL;
|
||||
linelen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
g_free(last);
|
||||
last = line;
|
||||
} while (TRUE);
|
||||
|
||||
if (last == NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
entry = ed_journal_entry_new_parse(last, NULL);
|
||||
|
||||
g_free(last);
|
||||
last = NULL;
|
||||
|
||||
if (entry == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
g_clear_object(&p->last);
|
||||
p->last = g_object_ref(entry);
|
||||
|
||||
g_clear_object(&entry);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ret = ed_error_success;
|
||||
|
||||
done:
|
||||
|
||||
g_clear_object(&reader);
|
||||
g_clear_object(&stream);
|
||||
g_clear_object(&file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static EDErrorCode
|
||||
ed_journal_file_load_(EDJournalFile *self,
|
||||
GError **error,
|
||||
@ -348,6 +540,10 @@ EDErrorCode ed_journal_file_open(EDJournalFile *file,
|
||||
return r;
|
||||
}
|
||||
|
||||
/* files may be empty */
|
||||
ed_journal_file_read_first_(file);
|
||||
ed_journal_file_read_last_(file);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -378,6 +574,20 @@ gchar const *ed_journal_file_get_gameversion(EDJournalFile *self)
|
||||
return p->gameversion;
|
||||
}
|
||||
|
||||
EDJournalEntry *ed_journal_file_get_first(EDJournalFile *self)
|
||||
{
|
||||
return_if_true(self == NULL, NULL);
|
||||
EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self);
|
||||
return p->first;
|
||||
}
|
||||
|
||||
EDJournalEntry *ed_journal_file_get_last(EDJournalFile *self)
|
||||
{
|
||||
return_if_true(self == NULL, NULL);
|
||||
EDJournalFilePrivate *p = ed_journal_file_get_instance_private(self);
|
||||
return p->last;
|
||||
}
|
||||
|
||||
gint ed_journal_file_compare(EDJournalFile *lhs, EDJournalFile *rhs)
|
||||
{
|
||||
return_if_true(lhs == NULL || rhs == NULL, 0);
|
||||
|
4
lib/tests/Journal.2024-04-18T061507.01.log
Normal file
4
lib/tests/Journal.2024-04-18T061507.01.log
Normal file
@ -0,0 +1,4 @@
|
||||
{ "timestamp":"2023-04-18T04:14:56Z", "event":"Fileheader", "part":1, "language":"English/UK", "Odyssey":true, "gameversion":"4.0.0.1477", "build":"r291050/r0 " }
|
||||
{ "timestamp":"2023-04-18T04:15:39Z", "event":"Commander", "FID":"F123456", "Name":"DeiMuata" }
|
||||
{ "timestamp":"2023-04-18T05:15:39Z", "event":"Something" }
|
||||
{ "timestamp":"2023-04-18T06:15:39Z", "event":"Shutdown" }
|
@ -146,6 +146,34 @@ static void test_valid_peek(void **state)
|
||||
g_clear_object(&file);
|
||||
}
|
||||
|
||||
static void test_first_last(void **state)
|
||||
{
|
||||
char const *filename = "Journal.2024-04-18T061507.01.log";
|
||||
|
||||
EDJournalFile *file = NULL;
|
||||
EDErrorCode ret = 0;
|
||||
EDJournalEntry *e = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
file = ed_journal_file_new();
|
||||
assert_non_null(file);
|
||||
|
||||
ret = ed_journal_file_open(file, filename, &error);
|
||||
|
||||
assert_null(error);
|
||||
assert_int_equal(ret, ed_error_success);
|
||||
|
||||
e = ed_journal_file_get_first(file);
|
||||
assert_non_null(e);
|
||||
assert_true(ed_journal_entry_is(e, "Fileheader"));
|
||||
|
||||
e = ed_journal_file_get_last(file);
|
||||
assert_non_null(e);
|
||||
assert_true(ed_journal_entry_is(e, "Shutdown"));
|
||||
|
||||
g_clear_object(&file);
|
||||
}
|
||||
|
||||
int main(int ac, char **av)
|
||||
{
|
||||
static const struct CMUnitTest tests[] = {
|
||||
@ -155,6 +183,7 @@ int main(int ac, char **av)
|
||||
cmocka_unit_test(test_new_datetime),
|
||||
cmocka_unit_test(test_old_datetime),
|
||||
cmocka_unit_test(test_valid_peek),
|
||||
cmocka_unit_test(test_first_last),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
|
Loading…
x
Reference in New Issue
Block a user