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

@@ -28,6 +28,7 @@
#include <glib.h>
#include <stdio.h>
#include <sys/types.h>
#include "database.h"
#include "xmpp/xmpp.h"
@@ -60,8 +61,44 @@ typedef struct
char* from_jid;
char* from_resource;
char* message;
off_t file_offset;
} ff_parsed_line_t;
// --- Sparse index for single-file lookup ---
#define FF_INDEX_STEP 500
typedef struct
{
off_t byte_offset;
gint64 timestamp_epoch;
} ff_index_entry_t;
typedef struct
{
time_t mtime;
off_t size;
ino_t inode;
} ff_file_stamp_t;
typedef struct
{
char* filepath;
ff_index_entry_t* entries;
size_t n_entries;
size_t cap_entries;
size_t total_lines;
ff_file_stamp_t stamp;
off_t cursor_offset;
int bom_len;
} ff_contact_state_t;
// State management
ff_contact_state_t* ff_state_new(const char* filepath);
void ff_state_free(ff_contact_state_t* state);
gboolean ff_state_ensure_fresh(ff_contact_state_t* state);
off_t ff_state_offset_for_time(ff_contact_state_t* state, const char* iso_time);
// --- Type conversion helpers ---
const char* ff_get_message_type_str(prof_msg_type_t type);
@@ -73,7 +110,7 @@ prof_enc_t ff_get_message_enc_type(const char* const encstr);
char* ff_jid_to_dir(const char* jid);
char* ff_get_contact_dir(const char* contact_barejid);
char* ff_get_log_path(const char* contact_barejid, GDateTime* dt);
char* ff_get_log_path(const char* contact_barejid);
gboolean ff_ensure_dir(const char* path);
// --- Escape / unescape ---