mirror of
https://git.jabber.space/devs/cproof.git
synced 2026-08-02 07:36:22 +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;
|
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
|
gboolean
|
||||||
cmd_history(ProfWin* window, const char* const command, gchar** args)
|
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...");
|
cons_show("Verifying history integrity...");
|
||||||
|
|
||||||
GSList* issues = log_database_verify_integrity(contact_jid);
|
GSList* issues = log_database_verify_integrity(contact_jid);
|
||||||
|
_show_integrity_issues(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);
|
|
||||||
}
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,46 +195,11 @@ typedef struct
|
|||||||
int skipped; // counter
|
int skipped; // counter
|
||||||
} export_ctx_t;
|
} export_ctx_t;
|
||||||
|
|
||||||
int
|
// Export a single contact's messages from SQLite to flatfile.
|
||||||
log_database_export_to_flatfile(const gchar* const contact_jid)
|
// 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
|
// 1. Read existing flatfile lines for dedup
|
||||||
GHashTable* seen_keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
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);
|
log_debug("export: no SQLite messages for %s", contact);
|
||||||
g_hash_table_destroy(seen_keys);
|
g_hash_table_destroy(seen_keys);
|
||||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
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
|
// 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_hash_table_destroy(seen_keys);
|
||||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
||||||
continue;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure directory
|
// Ensure directory
|
||||||
@@ -286,7 +251,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
|||||||
g_hash_table_destroy(seen_keys);
|
g_hash_table_destroy(seen_keys);
|
||||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
||||||
continue;
|
return -1;
|
||||||
}
|
}
|
||||||
FILE* fp = fdopen(tmp_fd, "w");
|
FILE* fp = fdopen(tmp_fd, "w");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
@@ -296,7 +261,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
|||||||
g_hash_table_destroy(seen_keys);
|
g_hash_table_destroy(seen_keys);
|
||||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_free);
|
||||||
continue;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the temp file
|
// Lock the temp file
|
||||||
@@ -397,6 +362,7 @@ log_database_export_to_flatfile(const gchar* const contact_jid)
|
|||||||
fclose(fp); // also releases flock
|
fclose(fp); // also releases flock
|
||||||
|
|
||||||
// Atomic rename
|
// Atomic rename
|
||||||
|
int result = -1;
|
||||||
if (rename(tmp_path, log_path) != 0) {
|
if (rename(tmp_path, log_path) != 0) {
|
||||||
log_error("export: rename %s -> %s failed: %s", tmp_path, log_path, strerror(errno));
|
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));
|
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 {
|
} else {
|
||||||
cons_show("Exported %s: %d new, %d skipped (already existed: %d)",
|
cons_show("Exported %s: %d new, %d skipped (already existed: %d)",
|
||||||
contact, contact_exported, contact_skipped, existing_count);
|
contact, contact_exported, contact_skipped, existing_count);
|
||||||
total_exported += contact_exported;
|
result = contact_exported;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_hash_table_destroy(seen_keys);
|
g_hash_table_destroy(seen_keys);
|
||||||
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
g_slist_free_full(existing, (GDestroyNotify)ff_parsed_line_free);
|
||||||
g_slist_free_full(sqlite_lines, (GDestroyNotify)message_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);
|
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);
|
g_date_time_unref(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
// Scan lines from an already-positioned file pointer, caching IDs and
|
||||||
_ff_state_build(ff_contact_state_t* state)
|
// 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");
|
size_t count = 0;
|
||||||
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);
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
off_t pos = ftell(fp);
|
off_t pos = ftell(fp);
|
||||||
gboolean truncated = FALSE;
|
gboolean truncated = FALSE;
|
||||||
@@ -197,16 +186,34 @@ _ff_state_build(ff_contact_state_t* state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
state->total_lines++;
|
state->total_lines++;
|
||||||
|
|
||||||
// Extract IDs for O(1) dedup/LMC cache
|
|
||||||
_ff_cache_line_ids(state, buf);
|
_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);
|
_ff_maybe_index_line(state, buf, pos);
|
||||||
}
|
}
|
||||||
msg_count++;
|
count++;
|
||||||
free(buf);
|
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);
|
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
|
// Use _ff_scan_lines for the shared read/index/cache loop.
|
||||||
// doesn't depend on the total from the initial build.
|
_ff_scan_lines(state, fp);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user