From d0f7b2a3649464df2dbc5484bd298430fae241fb Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 13:21:41 +0100 Subject: [PATCH] dm: expose delivery facts and archived conversation state to clients --- src/dm/dm_core.c | 30 +++++++++++++++++++++++------- tools/chatgui/db/db_manager.cpp | 14 +++++++++++++- tools/chatgui/db/db_manager.h | 3 +++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/dm/dm_core.c b/src/dm/dm_core.c index b0a9007d2..ea0ce48b6 100644 --- a/src/dm/dm_core.c +++ b/src/dm/dm_core.c @@ -1144,7 +1144,7 @@ int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf int failed = strbuf_addf(&json, "[") < 0; sqlite3_stmt* st = NULL; if (sqlite3_prepare_v2(dm->db, - "SELECT c.conv_id,peer_node_id,COALESCE(peer_name,''),last_ts,last_out_seq,last_in_seq,COALESCE(h.generation,1),mode,closed" + "SELECT c.conv_id,peer_node_id,COALESCE(peer_name,''),last_ts,last_out_seq,last_in_seq,COALESCE(h.generation,1),mode,closed,peer_party" " FROM dm_conversations c LEFT JOIN dm_history h USING(conv_id) WHERE COALESCE(h.hidden,0)=0" " ORDER BY last_ts DESC LIMIT 200", -1, &st, NULL) == SQLITE_OK) { @@ -1158,6 +1158,14 @@ int dm_list_conversations_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf uint64_t out_seq = (uint64_t)sqlite3_column_int64(st, 4); uint64_t in_seq = (uint64_t)sqlite3_column_int64(st, 5); char resolved[128]; + struct dm_party party; + struct devgroup_record record; + struct devgroup_profile profile; + if (sqlite3_column_bytes(st, 9) == DM_PARTY_WIRE_SIZE && + !dm_party_decode(sqlite3_column_blob(st, 9), &party) && party.kind == DM_PARTY_GROUP && + devgroup_latest(dm->db, party.ed, &record, &profile) == 1) { + snprintf(resolved, sizeof(resolved), "%s", profile.name); name = resolved; name_len = strlen(resolved); + } if (!name || !name_len) { resolved[0] = '\0'; chat_core_get_node_name(inst, peer, resolved, sizeof(resolved)); @@ -1208,7 +1216,12 @@ int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int c if (count <= 0 || count > 200) count = 200; 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," "COALESCE(f.ready,0) AS ready,COALESCE(f.downloading,0) AS downloading,f.receipt," - "COALESCE(f.local_path,'') AS path,COALESCE(f.plain_size,0) AS size" + "COALESCE(f.local_path,'') AS path,COALESCE(f.plain_size,0) AS size," + "(SELECT count(*) FROM dm_delivery d WHERE d.conv_id=m.conv_id AND d.author=m.author AND d.seq=m.seq) AS targets," + "(SELECT count(*) FROM dm_delivery d WHERE d.conv_id=m.conv_id AND d.author=m.author AND d.seq=m.seq AND d.receipt IS NOT NULL) AS delivered," + "(SELECT count(*) FROM dm_file_targets t WHERE t.id=f.id) AS file_targets," + "(SELECT count(*) FROM dm_file_targets t WHERE t.id=f.id AND t.received IS NOT NULL) AS received," + "(SELECT count(*) FROM dm_file_targets t WHERE t.id=f.id AND t.taken IS NOT NULL) AS opened" " FROM dm_messages m LEFT JOIN dm_outbox o ON m.author=?4 AND o.conv_id=m.conv_id AND o.seq=m.seq" " LEFT JOIN dm_files f ON f.conv_id=m.conv_id AND f.author=m.author AND f.seq=m.seq" " WHERE m.conv_id=?1 ORDER BY m.ts DESC,m.seq DESC,m.author DESC LIMIT ?2 OFFSET ?3) ORDER BY ts,seq,author"; @@ -1254,10 +1267,11 @@ int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int c snprintf(text, sizeof(text), "%s", attachment.name); } int ready = sqlite3_column_int(st, 7), downloading = sqlite3_column_int(st, 8); - int delivered = sqlite3_column_bytes(st, 9) == DM_MEDIA_RECEIPT_SIZE; + int file_total = sqlite3_column_int(st, 14), file_received = sqlite3_column_int(st, 15); + int delivered = file_total > 0 && file_received == file_total && author == inst->node_id; const char* path = ready == 1 ? (const char*)sqlite3_column_text(st, 10) : ""; - const char* status = delivered && dir ? "delivered" : ready == 1 ? "ready" : downloading ? "downloading" : - ready < 0 ? "expired" : "pending"; + const char* status = delivered ? "delivered" : ready == 1 ? "ready" : c.closed ? "unavailable" : + downloading ? "downloading" : ready < 0 ? "expired" : "pending"; char esc_ct[512], esc_data[DM_DATA_MAX * 2 + 8], esc_group[DM_DATA_MAX * 2 + 8], esc_path[2048]; json_escape(group_name, esc_group, sizeof(esc_group)); const char* display_data = invite ? text + strlen(group_name) + 1 : text; @@ -1265,11 +1279,13 @@ int dm_list_messages_json(struct UTUN_INSTANCE* inst, const char* conv_id, int c json_escape(path, esc_path, sizeof(esc_path)); if (strbuf_addf(&sb, "%s{\"dir\":%d,\"seq\":%llu,\"ts\":%llu,\"author\":\"%llu\",\"ct\":\"%s\",\"data\":\"%s\"," "\"pending\":%s,\"media_status\":\"%s\",\"media_path\":\"%s\",\"media_size\":%llu," - "\"tags\":{\"group_name\":\"%s\"}," + "\"delivery_total\":%d,\"delivery_received\":%d,\"file_total\":%d,\"file_received\":%d,\"file_opened\":%d," + "\"source\":%s,\"tags\":{\"group_name\":\"%s\"}," "\"content_type\":\"%s\",\"duration_ms\":%u,\"width\":%u,\"height\":%u,\"waveform\":\"%s\"}", first ? "" : ",", dir, (unsigned long long)seq, (unsigned long long)ts, (unsigned long long)author, esc_ct, esc_data, sqlite3_column_int(st, 6) ? "true" : "false", status, esc_path, - (unsigned long long)sqlite3_column_int64(st, 11), esc_group, + (unsigned long long)sqlite3_column_int64(st, 11), sqlite3_column_int(st, 12), sqlite3_column_int(st, 13), + file_total, file_received, sqlite3_column_int(st, 16), author == inst->node_id ? "true" : "false", esc_group, file ? attachment_content_type(attachment.kind) : "text/plain", attachment.duration_ms, attachment.width, attachment.height, attachment.waveform) < 0) goto fail; first = 0; diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index 759b2141e..a1fc3b316 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -9,6 +9,8 @@ #include #include "chat/chat_poll.h" #include "chat/group_avatar.h" +#include "dm/dm_identity.h" +#include "routing_layer/devgroup.h" /* ── helpers ── */ @@ -386,7 +388,8 @@ QList DbManager::getDmConversations() const { QList list; if (!m_db) return list; sqlite3_stmt* stmt = prepareOrNull( - "SELECT c.conv_id, peer_node_id, COALESCE(peer_name,''), last_ts, last_out_seq, last_in_seq, COALESCE(h.generation,1)" + "SELECT c.conv_id, peer_node_id, COALESCE(peer_name,''), last_ts, last_out_seq, last_in_seq, COALESCE(h.generation,1)," + " c.mode,c.closed,c.peer_party" " FROM dm_conversations c LEFT JOIN dm_history h USING(conv_id) WHERE COALESCE(h.hidden,0)=0 ORDER BY last_ts DESC"); if (!stmt) return list; int rc; @@ -399,6 +402,15 @@ QList DbManager::getDmConversations() const { c.lastOutSeq = sqlite3_column_int64(stmt, 4); c.lastInSeq = sqlite3_column_int64(stmt, 5); c.generation = (quint64)sqlite3_column_int64(stmt, 6); + c.mode = sqlite3_column_int(stmt, 7); c.closed = sqlite3_column_int(stmt, 8) != 0; + struct dm_party party; + if (sqlite3_column_bytes(stmt, 9) == DM_PARTY_WIRE_SIZE && + !dm_party_decode(static_cast(sqlite3_column_blob(stmt, 9)), &party)) { + c.peerGroup = party.kind == DM_PARTY_GROUP; + struct devgroup_record record; + struct devgroup_profile profile; + if (c.peerGroup && devgroup_latest(m_db, party.ed, &record, &profile) == 1) c.peerName = QString::fromUtf8(profile.name); + } list.append(c); } return finishRead(stmt, rc) ? list : QList(); diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h index 76d1a8f5c..9fb26f753 100644 --- a/tools/chatgui/db/db_manager.h +++ b/tools/chatgui/db/db_manager.h @@ -81,6 +81,9 @@ struct DmConversation { quint64 generation = 0; /* Поколение видимой истории: фиксируется в действиях PM до передачи ядру. */ quint64 peerNodeId = 0; QString peerName; + int mode = 0; // 0 — конкретное устройство, 1 — закреплённые стороны с подгруппами. + bool closed = false; // После выхода доступна только сохранённая история. + bool peerGroup = false; // Имя подгруппы не подменяется именем устройства-представителя. qint64 lastTs = 0; // Время последнего сообщения для сортировки UI. qint64 lastOutSeq = 0; // Последний исходящий seq. qint64 lastInSeq = 0; // Последний входящий seq; пространства направлений независимы.