From 0d6fd63e8f975da8d1d30ff1885853ebfb33f9df Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 23:57:52 +0100 Subject: [PATCH] Return attachment metadata as checked SQLite snapshots --- tools/chatgui/db/db_manager.cpp | 21 +++++++++++++++++++++ tools/chatgui/db/db_manager.h | 11 +++++++++++ tools/chatgui/src/messagelist.cpp | 22 ++++++++-------------- 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index 592732a3..0c994fc4 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -328,6 +328,27 @@ MessageRow DbManager::getMessageById(const QString& chId, qint64 msgId) const { return finishRead(stmt, rc) ? m : MessageRow(); } +/* Метаданные и состояние ключа читаются одним SELECT, курсор закрыт до передачи данных UI. */ +AttachmentRow DbManager::getAttachment(const QString& channel, const QByteArray& messageId) const { + AttachmentRow row; + if (messageId.size() != 32) { GUI_ERROR("attachment lookup: invalid key bytes=%d", int(messageId.size())); return row; } + sqlite3_stmt* st = prepareOrNull("SELECT a.ready,a.downloaded,a.decrypting,a.error,a.plain_path,a.info,a.content_type," + "COALESCE(r.state,-1),length(a.key)=32 FROM attachment_keys a LEFT JOIN attachment_key_requests r" + " ON r.channel=a.channel AND r.message_id=a.message_id AND r.incoming=0 WHERE a.channel=? AND a.message_id=?"); + if (!st) return row; + const QByteArray bytes = channel.toUtf8(); + int rc = sqlite3_bind_text(st, 1, bytes.constData(), bytes.size(), SQLITE_TRANSIENT); + if (rc == SQLITE_OK) rc = sqlite3_bind_blob(st, 2, messageId.constData(), messageId.size(), SQLITE_TRANSIENT); + if (rc == SQLITE_OK) rc = sqlite3_step(st); + if (rc == SQLITE_ROW) { + row.found = true; row.ready = sqlite3_column_int(st, 0); row.downloaded = sqlite3_column_int(st, 1); + row.decrypting = sqlite3_column_int(st, 2); row.error = sqlite3_column_int(st, 3); + row.path = colText(st, 4); row.info = colBlob(st, 5); row.contentType = colText(st, 6); + row.requestState = sqlite3_column_int(st, 7); row.hasKey = sqlite3_column_int(st, 8); + } + return finishRead(st, rc) ? row : AttachmentRow(); +} + /* ── DM-беседы (UI, read-only) ── */ /* Поиск ограничен таблицей выбранного канала; отсутствие оригинала допустимо при неполной синхронизации. */ diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h index ea3932a2..0e3aff34 100644 --- a/tools/chatgui/db/db_manager.h +++ b/tools/chatgui/db/db_manager.h @@ -50,6 +50,16 @@ struct MessageRow { bool isRead = false; }; +/* Самостоятельный снимок расшифровки вложения, без SQLite-указателей. */ +struct AttachmentRow { + bool found = false; // Строка получена целиком без SQL-ошибок. + bool ready = false, downloaded = false, decrypting = false, error = false; // Состояния подготовки plaintext. + bool hasKey = false; // В БД имеется 32-байтный ключ. + int requestState = -1; // Состояние исходящего запроса ключа; -1 — запроса нет. + QString path, contentType; // Plaintext-файл и его MIME; существование файла проверяет UI. + QByteArray info; // Кодированный attachment_info, валидируется attachment_decode. +}; + struct AccountRow { quint64 nodeId = 0; QString displayName; @@ -107,6 +117,7 @@ public: QList getMessagesSince(const QString& chId, qint64 sinceId) const; MessageRow getMessageById(const QString& chId, qint64 msgId) const; MessageRow getMessageByKey(const QString& chId, const QByteArray& key) const; + AttachmentRow getAttachment(const QString& channel, const QByteArray& messageId) const; // Ключ ровно 32 байта; ошибка → found=false. static QString messagePreview(const MessageRow& message); /* ── DM-беседы (UI, read-only) ── */ diff --git a/tools/chatgui/src/messagelist.cpp b/tools/chatgui/src/messagelist.cpp index 72b3b2a0..674a90ac 100644 --- a/tools/chatgui/src/messagelist.cpp +++ b/tools/chatgui/src/messagelist.cpp @@ -167,22 +167,17 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data, item->setData(0, MsgFileDownloadStateRole); const auto parts = QString::fromUtf8(body).split('|'); if (parts.size() > 3) { item->setData(parts[1].toLongLong(), MsgFileSizeRole); item->setData(parts[3].toInt(), MsgMediaNumBlocksRole); } - sqlite3_stmt* st = db ? db->prepareOrNull("SELECT a.ready,a.downloaded,a.decrypting,a.error,a.plain_path,a.info,a.content_type," - "COALESCE(r.state,-1),length(a.key)=32 FROM attachment_keys a LEFT JOIN attachment_key_requests r" - " ON r.channel=a.channel AND r.message_id=a.message_id AND r.incoming=0 WHERE a.channel=? AND a.message_id=?") : nullptr; - if (!st) return; - const QByteArray channel = channelId.toUtf8(); - sqlite3_bind_text(st, 1, channel.constData(), -1, SQLITE_STATIC); sqlite3_bind_blob(st, 2, messageId.constData(), messageId.size(), SQLITE_STATIC); - if (sqlite3_step(st) == SQLITE_ROW) { - const bool ready = sqlite3_column_int(st, 0), downloaded = sqlite3_column_int(st, 1), decrypting = sqlite3_column_int(st, 2); - const bool error = sqlite3_column_int(st, 3), key = sqlite3_column_int(st, 8); - const int state = sqlite3_column_int(st, 7); - const QString path = DbManager::colText(st, 4); + const AttachmentRow snapshot = db ? db->getAttachment(channelId, messageId) : AttachmentRow(); + if (snapshot.found) { + const bool ready = snapshot.ready, downloaded = snapshot.downloaded, decrypting = snapshot.decrypting; + const bool error = snapshot.error, key = snapshot.hasKey; + const int state = snapshot.requestState; + const QString path = snapshot.path; struct attachment_info info = {}; const bool available = ready && QFile::exists(path) && - !attachment_decode((const uint8_t*)sqlite3_column_blob(st, 5), sqlite3_column_bytes(st, 5), &info); + !attachment_decode((const uint8_t*)snapshot.info.constData(), snapshot.info.size(), &info); if (available) { - item->setData(DbManager::colText(st, 6), MsgContentTypeRole); + item->setData(snapshot.contentType, MsgContentTypeRole); item->setData(QString::fromUtf8(info.name), MsgFileDisplayNameRole); item->setData(QFileInfo(path).size(), MsgFileSizeRole); item->setData(QFileInfo(path).size(), MsgVoiceFileSizeRole); item->setData(path, MsgVoiceFileRole); item->setData(2, MsgFileDownloadStateRole); @@ -205,7 +200,6 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data, item->setData(downloaded && !error ? 1 : 0, MsgFileDownloadStateRole); } } - sqlite3_finalize(st); return; }