Browse Source

Expose bounded DM history snapshots with text and media delivery state

master
evgeny 2 weeks ago
parent
commit
1cdead18de
  1. 108
      src/dm/dm_core.c

108
src/dm/dm_core.c

@ -26,6 +26,7 @@
#include "../../lib/ll_queue.h" #include "../../lib/ll_queue.h"
#include "../../lib/debug_config.h" #include "../../lib/debug_config.h"
#include "../../lib/platform_compat.h" #include "../../lib/platform_compat.h"
#include "../../lib/strbuf.h"
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
@ -563,6 +564,7 @@ int dm_accept_receipt(struct UTUN_INSTANCE* inst, const uint8_t receipt[DM_RECEI
if (rc != SQLITE_DONE) goto fail; if (rc != SQLITE_DONE) goto fail;
sqlite3_finalize(st); sqlite3_finalize(st);
DEBUG_INFO(DEBUG_CATEGORY_DM, "%s: delivery confirmed conv=%s seq=%llu", DM_ID, conv_id, (unsigned long long)seq); DEBUG_INFO(DEBUG_CATEGORY_DM, "%s: delivery confirmed conv=%s seq=%llu", DM_ID, conv_id, (unsigned long long)seq);
dm_media_notify(inst, conv);
return 0; return 0;
fail: fail:
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: receipt database error rc=%d sql=%s", DM_ID, rc, sqlite3_errmsg(dm->db)); DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: receipt database error rc=%d sql=%s", DM_ID, rc, sqlite3_errmsg(dm->db));
@ -892,64 +894,66 @@ int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf
int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int count, int offset, int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int count, int offset,
char* buf, size_t buf_size, size_t* out_len) { char* buf, size_t buf_size, size_t* out_len) {
struct dm_state* dm = dm_of(inst); struct dm_state* dm = dm_of(inst);
if (!conv_id || !buf || !out_len || !dm || !dm->db) return -1; if (!conv_id || !buf || !out_len || !dm || !dm->db || offset < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: invalid messages snapshot request", DM_ID);
return -1;
}
*out_len = 0;
struct dm_conv c; struct dm_conv c;
if (dm_conv_load(dm, conv_id, &c) != 0) return -1; if (dm_conv_load(dm, conv_id, &c) != 0) return -1;
/* Ограниченный снимок не блокирует uasync на длинной истории. */
char sql[256]; if (count <= 0 || count > 200) count = 200;
if (count > 0) const char* sql = "SELECT * FROM (SELECT m.dir,m.seq,m.ts,m.author,m.ct,m.data,o.seq IS NOT NULL AS pending,"
snprintf(sql, sizeof(sql), "COALESCE(f.ready,0) AS ready,COALESCE(f.downloading,0) AS downloading,f.receipt,"
"SELECT dir,seq,ts,author,ct,data FROM dm_messages WHERE conv_id=? ORDER BY ts, seq LIMIT %d OFFSET %d", "COALESCE(f.local_path,'') AS path,COALESCE(f.plain_size,0) AS size"
count, offset); " FROM dm_messages m LEFT JOIN dm_outbox o ON m.dir=1 AND o.conv_id=m.conv_id AND o.seq=m.seq"
else " LEFT JOIN dm_files f ON f.conv_id=m.conv_id AND f.dir=m.dir AND f.seq=m.seq"
snprintf(sql, sizeof(sql), " WHERE m.conv_id=? ORDER BY m.ts DESC,m.seq DESC,m.dir DESC LIMIT ? OFFSET ?) ORDER BY ts,seq,dir";
"SELECT dir,seq,ts,author,ct,data FROM dm_messages WHERE conv_id=? ORDER BY ts, seq"); struct strbuf sb = strbuf_new();
int off = 0;
off += snprintf(buf + off, buf_size - (size_t)off, "[");
uint64_t conv_num = (uint64_t)strtoull(conv_id, NULL, 10); uint64_t conv_num = (uint64_t)strtoull(conv_id, NULL, 10);
sqlite3_stmt* st = NULL; sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(dm->db, sql, -1, &st, NULL) == SQLITE_OK) { if (sqlite3_prepare_v2(dm->db, sql, -1, &st, NULL) != SQLITE_OK || strbuf_addf(&sb, "[") < 0) goto fail;
sqlite3_bind_text(st, 1, conv_id, -1, SQLITE_STATIC); sqlite3_bind_text(st, 1, conv_id, -1, SQLITE_STATIC);
int first = 1; sqlite3_bind_int(st, 2, count); sqlite3_bind_int(st, 3, offset);
while (sqlite3_step(st) == SQLITE_ROW) { int first = 1, rc;
int dir = sqlite3_column_int(st, 0); while ((rc = sqlite3_step(st)) == SQLITE_ROW) {
uint64_t seq = (uint64_t)sqlite3_column_int64(st, 1); int dir = sqlite3_column_int(st, 0);
uint64_t ts = (uint64_t)sqlite3_column_int64(st, 2); uint64_t seq = (uint64_t)sqlite3_column_int64(st, 1), ts = (uint64_t)sqlite3_column_int64(st, 2);
uint64_t author = (uint64_t)sqlite3_column_int64(st, 3); uint64_t author = (uint64_t)sqlite3_column_int64(st, 3);
const char* ct = (const char*)sqlite3_column_text(st, 4); const char* ct = (const char*)sqlite3_column_text(st, 4);
const uint8_t* enc = (const uint8_t*)sqlite3_column_blob(st, 5); const uint8_t* enc = sqlite3_column_blob(st, 5);
int enc_len = sqlite3_column_bytes(st, 5); int enc_len = sqlite3_column_bytes(st, 5);
/* расшифровать (nonce детерминирован по conv_id+author+seq) */ char text[DM_DATA_MAX + 1];
char text[DM_DATA_MAX + 1]; size_t plen = 0;
text[0] = '\0'; uint8_t nonce[DM_NONCE_SIZE];
if (enc && enc_len >= (int)DM_TAG_SIZE) { dm_build_nonce(conv_num, author, seq, nonce);
uint8_t nonce[DM_NONCE_SIZE]; if (enc_len < DM_TAG_SIZE || enc_len > DM_DATA_MAX + DM_TAG_SIZE ||
dm_build_nonce(conv_num, author, seq, nonce); dm_decrypt(c.content_key, nonce, enc, (size_t)enc_len, (uint8_t*)text, &plen)) goto fail;
uint8_t* plain = u_malloc((size_t)enc_len); text[plen] = '\0';
size_t plen = 0; int ready = sqlite3_column_int(st, 7), downloading = sqlite3_column_int(st, 8);
if (dm_decrypt(c.content_key, nonce, enc, (size_t)enc_len, plain, &plen) == 0) { int delivered = sqlite3_column_bytes(st, 9) == DM_MEDIA_RECEIPT_SIZE;
if (plen >= sizeof(text)) plen = sizeof(text) - 1; const char* path = ready == 1 ? (const char*)sqlite3_column_text(st, 10) : "";
memcpy(text, plain, plen); const char* status = delivered && dir ? "delivered" : ready == 1 ? "ready" : downloading ? "downloading" :
text[plen] = '\0'; ready < 0 ? "expired" : "pending";
} char esc_ct[512], esc_data[DM_DATA_MAX * 2 + 8], esc_path[2048];
u_free(plain); json_escape(ct ? ct : "", esc_ct, sizeof(esc_ct)); json_escape(text, esc_data, sizeof(esc_data));
} json_escape(path, esc_path, sizeof(esc_path));
char esc_ct[64], esc_data[DM_DATA_MAX * 2 + 8]; if (strbuf_addf(&sb, "%s{\"dir\":%d,\"seq\":%llu,\"ts\":%llu,\"author\":\"%llu\",\"ct\":\"%s\",\"data\":\"%s\","
json_escape(ct ? ct : "", esc_ct, sizeof(esc_ct)); "\"pending\":%s,\"media_status\":\"%s\",\"media_path\":\"%s\",\"media_size\":%llu}", first ? "" : ",", dir,
json_escape(text, esc_data, sizeof(esc_data)); (unsigned long long)seq, (unsigned long long)ts, (unsigned long long)author, esc_ct, esc_data,
off += snprintf(buf + off, buf_size - (size_t)off, sqlite3_column_int(st, 6) ? "true" : "false", status, esc_path,
"%s{\"dir\":%d,\"seq\":%llu,\"ts\":%llu,\"author\":%llu,\"ct\":\"%s\",\"data\":\"%s\"}", (unsigned long long)sqlite3_column_int64(st, 11)) < 0) goto fail;
first ? "" : ",", dir, (unsigned long long)seq, (unsigned long long)ts, first = 0;
(unsigned long long)author, esc_ct, esc_data);
first = 0;
}
} }
if (st) sqlite3_finalize(st); if (rc != SQLITE_DONE || strbuf_addf(&sb, "]") < 0 || sb.len + 1 > buf_size) goto fail;
off += snprintf(buf + off, buf_size - (size_t)off, "]"); memcpy(buf, strbuf_str(&sb), sb.len + 1); *out_len = sb.len;
*out_len = (size_t)off; sqlite3_finalize(st); strbuf_free(&sb);
return 0; return 0;
fail:
DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: message snapshot failed conv=%s buffer=%zu required=%zu sql=%s", DM_ID,
conv_id, buf_size, sb.len + 1, sqlite3_errmsg(dm->db));
sqlite3_finalize(st); strbuf_free(&sb);
return -1;
} }
/* ── трамплины для gui_bridge (GUI → uasync) ── */ /* ── трамплины для gui_bridge (GUI → uasync) ── */

Loading…
Cancel
Save