From 905d30ceaf30af76a01b64eae4b337cd73ac498f Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 11:33:22 +0100 Subject: [PATCH] Distinguish failed avatar reads from committed empty blobs --- tools/chatgui/db/db_manager.cpp | 38 +++++++++++++++++++++++++++------ tools/chatgui/db/db_manager.h | 5 ++++- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index 94e70b74..ecae3b1f 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -10,6 +10,7 @@ #include #include #include "chat/chat_poll.h" +#include "chat/group_avatar.h" /* ── helpers ── */ @@ -119,14 +120,37 @@ QList DbManager::getChannels(ChannelSortMode sort) const { return list; } -QByteArray DbManager::getChannelAvatar(const QString& channelId) const { - sqlite3_stmt* st = prepareOrNull("SELECT avatar_data FROM channels WHERE channel_id=?"); if (!st) return {}; - const QByteArray id = channelId.toLatin1(); sqlite3_bind_text(st, 1, id.constData(), id.size(), SQLITE_TRANSIENT); +QByteArray DbManager::getChannelAvatar(const QString& channelId, bool* ok) const { + if (ok) *ok = false; + if (!m_db) { GUI_WARN("group avatar: database unavailable channel=%s", qPrintable(channelId)); return {}; } + sqlite3_stmt* st = nullptr; + const char* stage = "prepare"; + int rc = sqlite3_prepare_v2(m_db, "SELECT avatar_data FROM channels WHERE channel_id=?", -1, &st, nullptr); + const QByteArray id = channelId.toLatin1(); + if (rc == SQLITE_OK) { stage = "bind"; rc = sqlite3_bind_text(st, 1, id.constData(), id.size(), SQLITE_TRANSIENT); } + if (rc == SQLITE_OK) { stage = "step"; rc = sqlite3_step(st); } QByteArray bytes; - int rc = sqlite3_step(st); - if (rc == SQLITE_ROW) bytes = colBlob(st, 0); - else if (rc != SQLITE_DONE) GUI_ERROR("group avatar: database read failed channel=%s rc=%d", qPrintable(channelId), rc); - sqlite3_finalize(st); return bytes; + int type = -1, length = 0; + if (rc == SQLITE_ROW) { + stage = "blob"; type = sqlite3_column_type(st, 0); length = sqlite3_column_bytes(st, 0); + if (type != SQLITE_BLOB) rc = SQLITE_CORRUPT; + else if (length > GROUP_AVATAR_MAX) rc = SQLITE_TOOBIG; + else { + const char* blob = static_cast(sqlite3_column_blob(st, 0)); + if (length && !blob) rc = SQLITE_NOMEM; + else bytes = QByteArray(blob, length); + } + } + const int final = sqlite3_finalize(st); + if ((rc != SQLITE_ROW && rc != SQLITE_DONE) || final != SQLITE_OK) { + GUI_ERROR("group avatar: read channel=%s stage=%s rc=%d finalize=%d type=%d bytes=%d: %s", + qPrintable(channelId), stage, rc, final, type, length, sqlite3_errmsg(m_db)); return {}; + } + if (rc == SQLITE_DONE) { + GUI_DEBUG("group avatar: channel disappeared during read channel=%s", qPrintable(channelId)); return {}; + } + if (ok) *ok = true; + return bytes; } bool DbManager::isChannelMember(const QString& chId, quint64 nodeId) const { diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h index 3a21b588..df878e48 100644 --- a/tools/chatgui/db/db_manager.h +++ b/tools/chatgui/db/db_manager.h @@ -101,7 +101,10 @@ public: /* ── Каналы ── */ QList getChannels(ChannelSortMode sort = ChannelSortMode::Alphabetical) const; - QByteArray getChannelAvatar(const QString& channelId) const; + /* Читает BLOB аватара канала <=16 KiB. Возвращает самостоятельные байты; при ошибке/нет строки — пустой массив. + * Необязательный ok отличает успешный пустой BLOB (удаление) от отказа; bind/step/finalize проверяются. + * Хеш/версию и формат JPEG проверяет GroupAvatarManager, запись не меняется. */ + QByteArray getChannelAvatar(const QString& channelId, bool* ok = nullptr) const; QString getChannelLastMessage(const QString& channelId) const; int getUnreadCount(const QString& channelId, qint64 lastReadMsgId) const;