Return attachment metadata as checked SQLite snapshots

This commit is contained in:
evgeny committed 2026-10-28 00:59:00 +01:00
1 parent 034df2939c
commit 0d6fd63e8f
3 files changed
+40 -14

No files matched your search

+21
View File
@@ -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) ── */
/* Поиск ограничен таблицей выбранного канала; отсутствие оригинала допустимо при неполной синхронизации. */
+11
View File
@@ -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<MessageRow> 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) ── */
+8 -14
View File
@@ -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;
}