database_flatfile: single-file storage with sparse index

Replace per-contact directory structure with a single flat file per contact.
Each file uses pipe-delimited records with a sparse byte-offset index
that is rebuilt on demand and cached in memory.

- Rewrite _ff_write_record / _ff_read_records for single-file format
- Add sparse index (every Nth record) for O(log N) seek on history read
- Cursor-based pagination: _ff_get_previous_chat uses saved byte offset
- LMC corrections applied in-place during read (_ff_apply_lmc)
- Newline escaping (\n) in message bodies for line-based storage
- Simplify verify: parse + timestamp order + LMC reference check
- Update parser helpers for new field layout
This commit is contained in:
2026-02-19 16:09:23 +03:00
parent d25f90899c
commit 16ae7fb85c
4 changed files with 608 additions and 377 deletions

View File

@@ -167,17 +167,15 @@ ff_get_contact_dir(const char* contact_barejid)
return result;
}
// Get the log file path for a contact on a specific date:
// {contact_dir}/{YYYY_MM_DD}.log
// Get the single log file path for a contact: {contact_dir}/history.log
char*
ff_get_log_path(const char* contact_barejid, GDateTime* dt)
ff_get_log_path(const char* contact_barejid)
{
auto_gchar gchar* contact_dir = ff_get_contact_dir(contact_barejid);
if (!contact_dir)
return NULL;
auto_gchar gchar* date_str = g_date_time_format(dt, "%Y_%m_%d");
char* result = g_strdup_printf("%s/%s.log", contact_dir, date_str);
char* result = g_strdup_printf("%s/history.log", contact_dir);
return result;
}
@@ -616,6 +614,7 @@ ff_parse_line(const char* line)
}
ff_parsed_line_t* result = g_malloc0(sizeof(ff_parsed_line_t));
result->file_offset = -1;
// Parse timestamp — everything up to first space followed by '['
char* bracket_start = strchr(work, '[');