Return complete channel snapshots and distinguish database errors from empty history
This commit is contained in:
1 parent
57227b331d
commit
05ab3b8054
2 files changed
+60
-87
No files matched your search
@@ -433,98 +433,70 @@ char* utun_bridge_get_channels_json(void) {
|
||||
struct UTUN_INSTANCE* inst = instance_lite_get_instance();
|
||||
sqlite3* db = chat_core_get_db(inst);
|
||||
if (!chat_core_is_initialized(inst) || !db) {
|
||||
bridge_log(BLEV_WARN, "get_channels: not initialized db=%p initialized=%d",
|
||||
(void*)db, chat_core_is_initialized(inst));
|
||||
return u_strdup("[]");
|
||||
bridge_log(BLEV_WARN, "get_channels: chat/database unavailable"); return NULL;
|
||||
}
|
||||
size_t cap = 4096, pos = 1;
|
||||
char* json = u_malloc(cap);
|
||||
if (!json) return u_strdup("[]");
|
||||
json[0] = '[';
|
||||
|
||||
sqlite3_stmt* st = NULL;
|
||||
int rc = sqlite3_prepare_v2(db, "SELECT channel_id, name, last_msg_at FROM channels ORDER BY last_msg_at DESC, name ASC", -1, &st, NULL);
|
||||
if (rc != SQLITE_OK) { u_free(json); return u_strdup("[]"); }
|
||||
|
||||
int first = 1;
|
||||
while (sqlite3_step(st) == SQLITE_ROW) {
|
||||
const char* ch_id = (const char*)sqlite3_column_text(st, 0);
|
||||
const char* name = (const char*)sqlite3_column_text(st, 1);
|
||||
int64_t last_msg_at = sqlite3_column_int64(st, 2);
|
||||
if (!ch_id) continue;
|
||||
|
||||
char tbl_msg[80], tbl_peers[80], last_msg[160];
|
||||
int last_encrypted = 0;
|
||||
msg_table_name(ch_id, tbl_msg, sizeof(tbl_msg));
|
||||
peers_table_name(ch_id, tbl_peers, sizeof(tbl_peers));
|
||||
last_msg[0] = '\0';
|
||||
|
||||
sqlite3_stmt* lm = NULL;
|
||||
char sql[256];
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id, data FROM \"%s\" ORDER BY id DESC LIMIT 1", tbl_msg);
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &lm, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(lm) == SQLITE_ROW) {
|
||||
int64_t author = sqlite3_column_int64(lm, 0);
|
||||
const uint8_t* dptr = sqlite3_column_blob(lm, 1);
|
||||
int dlen = sqlite3_column_bytes(lm, 1);
|
||||
char body[160] = "";
|
||||
if (dptr && dlen > 0) {
|
||||
const char* dp = (const char*)dptr;
|
||||
char ct[32] = "text/plain";
|
||||
const char* cts = strstr(dp, "\"ct\":\"");
|
||||
if (cts) { cts += 6; char* cte = strchr((char*)cts, '"'); if (cte) { size_t tlc = (size_t)(cte - cts); if (tlc < sizeof(ct)) { memcpy(ct, cts, tlc); ct[tlc] = '\0'; } } }
|
||||
last_encrypted = !strcmp(ct, ENCRYPTED_ATTACHMENT_TYPE);
|
||||
const char* ds = strstr(dp, "\"d\":\"");
|
||||
if (ds) { ds += 5; char* de = strchr((char*)ds, '"'); if (de) { size_t tl = (size_t)(de - ds); if (tl > 511) tl = 511; char d[512]; memcpy(d, ds, tl); d[tl] = '\0'; bridge_format_preview_body(ct, d, body, sizeof(body)); } }
|
||||
}
|
||||
char author_name[128];
|
||||
bridge_get_node_name(db, (uint64_t)author, ch_id, author_name, sizeof(author_name));
|
||||
if (!author_name[0])
|
||||
snprintf(author_name, sizeof(author_name), "0x%016llx", (unsigned long long)author);
|
||||
snprintf(last_msg, sizeof(last_msg), "%s: %s", author_name, body);
|
||||
}
|
||||
sqlite3_finalize(lm);
|
||||
struct strbuf out = strbuf_new();
|
||||
sqlite3_stmt *st = NULL, *last = NULL, *peers = NULL;
|
||||
int rc = sqlite3_prepare_v2(db, "SELECT channel_id,name,last_msg_at FROM channels ORDER BY last_msg_at DESC,name ASC", -1, &st, NULL);
|
||||
if (rc != SQLITE_OK || strbuf_addf(&out, "[") < 0) goto failed;
|
||||
unsigned count = 0;
|
||||
while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
|
||||
const char* channel = (const char*)sqlite3_column_text(st, 0);
|
||||
const char* name = (const char*)sqlite3_column_text(st, 1);
|
||||
if (!channel || !channel[0]) { bridge_log(BLEV_ERROR, "get_channels: invalid stored channel ID"); goto failed; }
|
||||
char messages_table[80], peers_table[80], sql[512], preview[160] = "";
|
||||
msg_table_name(channel, messages_table, sizeof(messages_table));
|
||||
peers_table_name(channel, peers_table, sizeof(peers_table));
|
||||
snprintf(sql, sizeof(sql), "SELECT node_id,"
|
||||
"CASE WHEN json_valid(CAST(data AS TEXT)) THEN json_extract(CAST(data AS TEXT),'$.ct') END,"
|
||||
"CASE WHEN json_valid(CAST(data AS TEXT)) THEN json_extract(CAST(data AS TEXT),'$.d') END"
|
||||
" FROM \"%s\" ORDER BY id DESC LIMIT 1", messages_table);
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &last, NULL) != SQLITE_OK) goto failed;
|
||||
int last_rc = sqlite3_step(last), encrypted = 0;
|
||||
if (last_rc == SQLITE_ROW) {
|
||||
uint64_t author = (uint64_t)sqlite3_column_int64(last, 0);
|
||||
const char* type = (const char*)sqlite3_column_text(last, 1);
|
||||
const char* data = (const char*)sqlite3_column_text(last, 2);
|
||||
char body[160] = "", author_name[128];
|
||||
encrypted = type && !strcmp(type, ENCRYPTED_ATTACHMENT_TYPE);
|
||||
bridge_format_preview_body(type, data ? data : "", body, sizeof(body));
|
||||
bridge_get_node_name(db, author, channel, author_name, sizeof(author_name));
|
||||
if (!author_name[0]) snprintf(author_name, sizeof(author_name), "0x%016llx", (unsigned long long)author);
|
||||
snprintf(preview, sizeof(preview), "%s: %s", author_name, body);
|
||||
} else if (last_rc != SQLITE_DONE) goto failed;
|
||||
sqlite3_finalize(last); last = NULL;
|
||||
snprintf(sql, sizeof(sql), "SELECT DISTINCT node_id FROM \"%s\"", peers_table);
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &peers, NULL) != SQLITE_OK) goto failed;
|
||||
int online = 0, peer_rc;
|
||||
while ((peer_rc = sqlite3_step(peers)) == SQLITE_ROW) {
|
||||
uint64_t node = (uint64_t)sqlite3_column_int64(peers, 0);
|
||||
if (node == inst->node_id) continue;
|
||||
struct ETCP_CONN* connection = instance_find_conn(inst, node);
|
||||
if (connection && connection->initialized && connection->links_up) online++;
|
||||
}
|
||||
|
||||
int online = 0;
|
||||
uint64_t my_id = inst->node_id;
|
||||
sqlite3_stmt* pc = NULL;
|
||||
snprintf(sql, sizeof(sql), "SELECT DISTINCT node_id FROM \"%s\"", tbl_peers);
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &pc, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(pc) == SQLITE_ROW) {
|
||||
uint64_t nid = (uint64_t)sqlite3_column_int64(pc, 0);
|
||||
if (nid == my_id) continue;
|
||||
if (inst) { struct ETCP_CONN* c = instance_find_conn(inst, nid); if (c && c->initialized && c->links_up) online++; }
|
||||
else online++;
|
||||
}
|
||||
sqlite3_finalize(pc);
|
||||
}
|
||||
|
||||
char* esc_name = json_escape_alloc(name ? name : ch_id);
|
||||
char* esc_msg = json_escape_alloc(last_msg);
|
||||
const char* sep = first ? "" : ",";
|
||||
first = 0;
|
||||
|
||||
size_t needed = snprintf(NULL, 0, "%s{\"id\":\"%s\",\"name\":\"%s\",\"lastMsgAt\":%lld,\"lastMsg\":\"%s\","
|
||||
"\"peersOnline\":%d,\"lastMsgEncrypted\":%s}", sep, ch_id, esc_name, (long long)last_msg_at, esc_msg, online,
|
||||
last_encrypted ? "true" : "false");
|
||||
while (pos + needed + 2 > cap) { char* tmp = u_realloc(json, cap * 2); if (!tmp) break; json = tmp; cap *= 2; }
|
||||
if (pos + needed + 2 <= cap)
|
||||
pos += (size_t)snprintf(json + pos, cap - pos, "%s{\"id\":\"%s\",\"name\":\"%s\",\"lastMsgAt\":%lld,\"lastMsg\":\"%s\","
|
||||
"\"peersOnline\":%d,\"lastMsgEncrypted\":%s}", sep, ch_id, esc_name, (long long)last_msg_at, esc_msg, online,
|
||||
last_encrypted ? "true" : "false");
|
||||
u_free(esc_msg);
|
||||
u_free(esc_name);
|
||||
if (peer_rc != SQLITE_DONE) goto failed;
|
||||
sqlite3_finalize(peers); peers = NULL;
|
||||
if (strbuf_addf(&out, "%s{\"id\":", count++ ? "," : "") < 0 ||
|
||||
strbuf_add_json_string(&out, channel) < 0 || strbuf_addf(&out, ",\"name\":") < 0 ||
|
||||
strbuf_add_json_string(&out, name ? name : channel) < 0 ||
|
||||
strbuf_addf(&out, ",\"lastMsgAt\":%lld,\"lastMsg\":", (long long)sqlite3_column_int64(st, 2)) < 0 ||
|
||||
strbuf_add_json_string(&out, preview) < 0 ||
|
||||
strbuf_addf(&out, ",\"peersOnline\":%d,\"lastMsgEncrypted\":%s}", online, encrypted ? "true" : "false") < 0) goto failed;
|
||||
}
|
||||
if (rc != SQLITE_DONE || strbuf_addf(&out, "]") < 0) goto failed;
|
||||
sqlite3_finalize(st);
|
||||
|
||||
json[pos++] = ']'; json[pos] = '\0';
|
||||
return json;
|
||||
return strbuf_detach(&out);
|
||||
failed:
|
||||
bridge_log(BLEV_ERROR, "get_channels: complete snapshot failed: %s", sqlite3_errmsg(db));
|
||||
sqlite3_finalize(last); sqlite3_finalize(peers); sqlite3_finalize(st); strbuf_free(&out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* utun_bridge_get_messages_json(const char* channel_id, int limit, const uint8_t* anchor, int exact) {
|
||||
struct UTUN_INSTANCE* inst = instance_lite_get_instance(); sqlite3* db = chat_core_get_db(inst);
|
||||
if (!chat_core_is_initialized(inst) || !db || !channel_id || !channel_id[0]) return u_strdup("[]");
|
||||
if (!chat_core_is_initialized(inst) || !db || !channel_id || !channel_id[0]) {
|
||||
bridge_log(BLEV_WARN, "get_messages: chat/database/channel unavailable"); return NULL;
|
||||
}
|
||||
char table[80], sql[2048], selection[1024]; msg_table_name(channel_id, table, sizeof(table));
|
||||
if (exact && anchor) snprintf(selection, sizeof(selection), "SELECT * FROM \"%s\" WHERE message_id=?2", table);
|
||||
else if (anchor) snprintf(selection, sizeof(selection),
|
||||
@@ -544,7 +516,7 @@ char* utun_bridge_get_messages_json(const char* channel_id, int limit, const uin
|
||||
" ORDER BY m.timestamp,m.message_id", selection);
|
||||
sqlite3_stmt* st = NULL;
|
||||
if (sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "bridge: messages query ch=%s: %s", channel_id, sqlite3_errmsg(db)); return u_strdup("[]");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "bridge: messages query ch=%s: %s", channel_id, sqlite3_errmsg(db)); return NULL;
|
||||
}
|
||||
sqlite3_bind_int(st, 1, limit > 0 ? limit : 50);
|
||||
if (anchor) sqlite3_bind_blob(st, 2, anchor, 32, SQLITE_STATIC);
|
||||
@@ -656,7 +628,7 @@ char* utun_bridge_get_messages_json(const char* channel_id, int limit, const uin
|
||||
sqlite3_finalize(st);
|
||||
if (result < 0 || step != SQLITE_DONE || strbuf_addf(&out, "]") < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "bridge: messages snapshot failed ch=%s: %s", channel_id, sqlite3_errmsg(db));
|
||||
strbuf_free(&out); return u_strdup("[]");
|
||||
strbuf_free(&out); return NULL;
|
||||
}
|
||||
return strbuf_detach(&out);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,8 @@ void utun_bridge_connect_channel(const char* channel_id);
|
||||
/* Однократная попытка подключения к конкретному узлу канала (ручной "Connect"). */
|
||||
void utun_bridge_connect_member_node(const char* channel_id, uint64_t node_id);
|
||||
|
||||
/* Синхронные JSON-снимки; память освобождать u_free. */
|
||||
/* Синхронные JSON-снимки в потоке ядра; память освобождать u_free.
|
||||
* [] — подтверждённый пустой снимок, NULL — ошибка SQL/памяти/неинициализированное ядро; частичного результата нет. */
|
||||
|
||||
char* utun_bridge_get_channels_json(void);
|
||||
/* limit<=0 → 50; anchor=NULL — последние сообщения, иначе окно вокруг 32-байтового message_id.
|
||||
|
||||
Reference in new issue
Block a user