mirror of
https://git.jabber.space/devs/cproof.git
synced 2026-08-04 23:36:21 +00:00
refactor: extract helpers for verify print, scan loop, and per-contact export
- extract _show_integrity_issues() from cmd_history verify block - extract _ff_scan_lines() shared by _ff_state_build and _ff_state_extend - extract _export_one_contact() from log_database_export_to_flatfile loop
This commit is contained in:
@@ -6729,6 +6729,54 @@ cmd_logging(ProfWin* window, const char* const command, gchar** args)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
_show_integrity_issues(GSList* issues)
|
||||
{
|
||||
int errors = 0, warnings = 0, infos = 0;
|
||||
for (GSList* l = issues; l; l = l->next) {
|
||||
integrity_issue_t* issue = l->data;
|
||||
const char* level_str;
|
||||
switch (issue->level) {
|
||||
case INTEGRITY_ERROR:
|
||||
level_str = "ERROR";
|
||||
errors++;
|
||||
break;
|
||||
case INTEGRITY_WARNING:
|
||||
level_str = "WARN";
|
||||
warnings++;
|
||||
break;
|
||||
case INTEGRITY_INFO:
|
||||
level_str = "INFO";
|
||||
infos++;
|
||||
break;
|
||||
default:
|
||||
level_str = "???";
|
||||
break;
|
||||
}
|
||||
if (issue->line > 0) {
|
||||
if (issue->level == INTEGRITY_ERROR) {
|
||||
cons_show_error("[%s] %s:%d — %s", level_str, issue->file, issue->line, issue->message);
|
||||
} else {
|
||||
cons_show("[%s] %s:%d — %s", level_str, issue->file, issue->line, issue->message);
|
||||
}
|
||||
} else {
|
||||
if (issue->level == INTEGRITY_ERROR) {
|
||||
cons_show_error("[%s] %s — %s", level_str, issue->file, issue->message);
|
||||
} else {
|
||||
cons_show("[%s] %s — %s", level_str, issue->file, issue->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!issues) {
|
||||
cons_show("Verification complete: no issues found.");
|
||||
} else {
|
||||
cons_show("Verification complete: %d error(s), %d warning(s), %d info(s).",
|
||||
errors, warnings, infos);
|
||||
g_slist_free_full(issues, (GDestroyNotify)integrity_issue_free);
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
cmd_history(ProfWin* window, const char* const command, gchar** args)
|
||||
{
|
||||
@@ -6773,50 +6821,7 @@ cmd_history(ProfWin* window, const char* const command, gchar** args)
|
||||
cons_show("Verifying history integrity...");
|
||||
|
||||
GSList* issues = log_database_verify_integrity(contact_jid);
|
||||
|
||||
int errors = 0, warnings = 0, infos = 0;
|
||||
for (GSList* l = issues; l; l = l->next) {
|
||||
integrity_issue_t* issue = l->data;
|
||||
const char* level_str;
|
||||
switch (issue->level) {
|
||||
case INTEGRITY_ERROR:
|
||||
level_str = "ERROR";
|
||||
errors++;
|
||||
break;
|
||||
case INTEGRITY_WARNING:
|
||||
level_str = "WARN";
|
||||
warnings++;
|
||||
break;
|
||||
case INTEGRITY_INFO:
|
||||
level_str = "INFO";
|
||||
infos++;
|
||||
break;
|
||||
default:
|
||||
level_str = "???";
|
||||
break;
|
||||
}
|
||||
if (issue->line > 0) {
|
||||
if (issue->level == INTEGRITY_ERROR) {
|
||||
cons_show_error("[%s] %s:%d — %s", level_str, issue->file, issue->line, issue->message);
|
||||
} else {
|
||||
cons_show("[%s] %s:%d — %s", level_str, issue->file, issue->line, issue->message);
|
||||
}
|
||||
} else {
|
||||
if (issue->level == INTEGRITY_ERROR) {
|
||||
cons_show_error("[%s] %s — %s", level_str, issue->file, issue->message);
|
||||
} else {
|
||||
cons_show("[%s] %s — %s", level_str, issue->file, issue->message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!issues) {
|
||||
cons_show("Verification complete: no issues found.");
|
||||
} else {
|
||||
cons_show("Verification complete: %d error(s), %d warning(s), %d info(s).",
|
||||
errors, warnings, infos);
|
||||
g_slist_free_full(issues, (GDestroyNotify)integrity_issue_free);
|
||||
}
|
||||
_show_integrity_issues(issues);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,46 +195,11 @@ typedef struct
|
||||
int skipped; // counter
|
||||
} export_ctx_t;
|
||||
|
||||
int
|
||||
log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
// Export a single contact's messages from SQLite to flatfile.
|
||||
// Returns the number of newly exported messages, or -1 on error/skip.
|
||||
static int
|
||||
_export_one_contact(const char* contact)
|
||||
{
|
||||
const Jid* myjid = connection_get_jid();
|
||||
if (!myjid || !myjid->barejid) {
|
||||
cons_show_error("Export failed: not connected.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_flatfile_account_jid) {
|
||||
// Flatfile backend not initialized — init it temporarily
|
||||
// We need g_flatfile_account_jid for path construction
|
||||
g_flatfile_account_jid = g_strdup(myjid->barejid);
|
||||
}
|
||||
|
||||
// Get the SQLite backend
|
||||
db_backend_t* sqlite_be = db_backend_sqlite();
|
||||
if (!sqlite_be) {
|
||||
cons_show_error("Export failed: SQLite backend not available.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Determine contact list
|
||||
GSList* contacts = NULL;
|
||||
if (contact_jid) {
|
||||
contacts = g_slist_append(contacts, g_strdup(contact_jid));
|
||||
} else {
|
||||
contacts = db_sqlite_list_contacts();
|
||||
if (!contacts) {
|
||||
cons_show("No contacts found in SQLite database.");
|
||||
return 0;
|
||||
}
|
||||
cons_show("Exporting %d contact(s) to flat-file format...", g_slist_length(contacts));
|
||||
}
|
||||
|
||||
int total_exported = 0;
|
||||
|
||||
for (GSList* c = contacts; c; c = c->next) {
|
||||
const char* contact = c->data;
|
||||
|
||||
// 1. Read existing flatfile lines for dedup
|
||||
GHashTable* seen_keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
|
||||
@@ -253,7 +218,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
log_debug("export: no SQLite messages for %s", contact);
|
||||
g_hash_table_destroy(seen_keys);
|
||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 3. Merge: write existing flatfile lines + new SQLite lines to temp file
|
||||
@@ -262,7 +227,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
g_hash_table_destroy(seen_keys);
|
||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Ensure directory
|
||||
@@ -286,7 +251,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
g_hash_table_destroy(seen_keys);
|
||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
FILE* fp = fdopen(tmp_fd, "w");
|
||||
if (!fp) {
|
||||
@@ -296,7 +261,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
g_hash_table_destroy(seen_keys);
|
||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Lock the temp file
|
||||
@@ -397,6 +362,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
fclose(fp); // also releases flock
|
||||
|
||||
// Atomic rename
|
||||
int result = -1;
|
||||
if (rename(tmp_path, log_path) != 0) {
|
||||
log_error("export: rename %s -> %s failed: %s", tmp_path, log_path, strerror(errno));
|
||||
cons_show_error("Export failed for %s: could not rename temp file (%s)", contact, strerror(errno));
|
||||
@@ -404,12 +370,57 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
} else {
|
||||
cons_show("Exported %s: %d new, %d skipped (already existed: %d)",
|
||||
contact, contact_exported, contact_skipped, existing_count);
|
||||
total_exported += contact_exported;
|
||||
result = contact_exported;
|
||||
}
|
||||
|
||||
g_hash_table_destroy(seen_keys);
|
||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
log_database_export_to_flatfile(const gchar* const contact_jid)
|
||||
{
|
||||
const Jid* myjid = connection_get_jid();
|
||||
if (!myjid || !myjid->barejid) {
|
||||
cons_show_error("Export failed: not connected.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!g_flatfile_account_jid) {
|
||||
// Flatfile backend not initialized — init it temporarily
|
||||
// We need g_flatfile_account_jid for path construction
|
||||
g_flatfile_account_jid = g_strdup(myjid->barejid);
|
||||
}
|
||||
|
||||
// Get the SQLite backend
|
||||
db_backend_t* sqlite_be = db_backend_sqlite();
|
||||
if (!sqlite_be) {
|
||||
cons_show_error("Export failed: SQLite backend not available.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Determine contact list
|
||||
GSList* contacts = NULL;
|
||||
if (contact_jid) {
|
||||
contacts = g_slist_append(contacts, g_strdup(contact_jid));
|
||||
} else {
|
||||
contacts = db_sqlite_list_contacts();
|
||||
if (!contacts) {
|
||||
cons_show("No contacts found in SQLite database.");
|
||||
return 0;
|
||||
}
|
||||
cons_show("Exporting %d contact(s) to flat-file format...", g_slist_length(contacts));
|
||||
}
|
||||
|
||||
int total_exported = 0;
|
||||
|
||||
for (GSList* c = contacts; c; c = c->next) {
|
||||
const char* contact = c->data;
|
||||
int exported = _export_one_contact(contact);
|
||||
if (exported > 0)
|
||||
total_exported += exported;
|
||||
}
|
||||
|
||||
g_slist_free_full(contacts, g_free);
|
||||
|
||||
@@ -163,23 +163,12 @@ _ff_maybe_index_line(ff_contact_state_t* state, const char* buf, off_t pos)
|
||||
g_date_time_unref(dt);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_ff_state_build(ff_contact_state_t* state)
|
||||
// Scan lines from an already-positioned file pointer, caching IDs and
|
||||
// building index entries. Returns the number of messages scanned.
|
||||
static size_t
|
||||
_ff_scan_lines(ff_contact_state_t* state, FILE* fp)
|
||||
{
|
||||
FILE* fp = fopen(state->filepath, "r");
|
||||
if (!fp)
|
||||
return FALSE;
|
||||
|
||||
state->bom_len = ff_skip_bom(fp);
|
||||
|
||||
state->n_entries = 0;
|
||||
state->total_lines = 0;
|
||||
size_t msg_count = 0;
|
||||
|
||||
// Clear ID caches for full rebuild
|
||||
g_hash_table_remove_all(state->archive_ids);
|
||||
g_hash_table_remove_all(state->stanza_senders);
|
||||
|
||||
size_t count = 0;
|
||||
while (1) {
|
||||
off_t pos = ftell(fp);
|
||||
gboolean truncated = FALSE;
|
||||
@@ -197,16 +186,34 @@ _ff_state_build(ff_contact_state_t* state)
|
||||
}
|
||||
|
||||
state->total_lines++;
|
||||
|
||||
// Extract IDs for O(1) dedup/LMC cache
|
||||
_ff_cache_line_ids(state, buf);
|
||||
|
||||
if (msg_count % FF_INDEX_STEP == 0) {
|
||||
if (count % FF_INDEX_STEP == 0) {
|
||||
_ff_maybe_index_line(state, buf, pos);
|
||||
}
|
||||
msg_count++;
|
||||
count++;
|
||||
free(buf);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_ff_state_build(ff_contact_state_t* state)
|
||||
{
|
||||
FILE* fp = fopen(state->filepath, "r");
|
||||
if (!fp)
|
||||
return FALSE;
|
||||
|
||||
state->bom_len = ff_skip_bom(fp);
|
||||
|
||||
state->n_entries = 0;
|
||||
state->total_lines = 0;
|
||||
|
||||
// Clear ID caches for full rebuild
|
||||
g_hash_table_remove_all(state->archive_ids);
|
||||
g_hash_table_remove_all(state->stanza_senders);
|
||||
|
||||
_ff_scan_lines(state, fp);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
@@ -247,37 +254,8 @@ _ff_state_extend(ff_contact_state_t* state)
|
||||
}
|
||||
}
|
||||
|
||||
// Use a local counter for new messages so index step alignment
|
||||
// doesn't depend on the total from the initial build.
|
||||
size_t new_count = 0;
|
||||
|
||||
while (1) {
|
||||
off_t pos = ftell(fp);
|
||||
gboolean truncated = FALSE;
|
||||
char* buf = ff_readline(fp, &truncated);
|
||||
if (!buf)
|
||||
break;
|
||||
if (truncated) {
|
||||
free(buf);
|
||||
break;
|
||||
}
|
||||
|
||||
if (buf[0] == '\0' || buf[0] == '#') {
|
||||
free(buf);
|
||||
continue;
|
||||
}
|
||||
|
||||
state->total_lines++;
|
||||
|
||||
// Extract IDs for O(1) dedup/LMC cache
|
||||
_ff_cache_line_ids(state, buf);
|
||||
|
||||
if (new_count % FF_INDEX_STEP == 0) {
|
||||
_ff_maybe_index_line(state, buf, pos);
|
||||
}
|
||||
new_count++;
|
||||
free(buf);
|
||||
}
|
||||
// Use _ff_scan_lines for the shared read/index/cache loop.
|
||||
_ff_scan_lines(state, fp);
|
||||
|
||||
fclose(fp);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user