Remove obsolete desktop DB sync and GUI writes and enforce read-only queries

This commit is contained in:
evgeny committed 2026-10-28 00:59:00 +01:00
1 parent f87ee63a1d
commit ce133a6acf
2 files changed
+20 -229

No files matched your search

+18 -199
View File
@@ -1,8 +1,6 @@
#include "db_manager.h"
#include "../src/debug_ui.h"
#include "../../lib/debug_config.h"
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <cstring>
#include <QSet>
#include <QElapsedTimer>
@@ -36,12 +34,6 @@ QString DbManager::peersTableName(const QString& chId) const {
return QStringLiteral("peers_") + sanitizeTableName(chId);
}
uint64_t DbManager::computeDatahash(const QByteArray& data) {
uint8_t hash[32];
SHA256((const unsigned char*)data.constData(), data.size(), hash);
uint64_t dh; memcpy(&dh, hash, 8); return dh;
}
/* ── lifecycle ── */
DbManager::DbManager(QObject* parent)
@@ -50,22 +42,36 @@ DbManager::DbManager(QObject* parent)
}
DbManager::~DbManager() {
for (auto it = m_cursors.begin(); it != m_cursors.end(); ++it)
sqlite3_finalize(it.value());
m_cursors.clear();
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "DbManager: destroyed");
}
sqlite3_stmt* DbManager::prepareOrNull(const char* sql) const {
if (!m_db) { GUI_DEBUG("read skipped: database unavailable sql=%s", sql); return nullptr; }
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
const char* err = sqlite3_errmsg(m_db);
if (!strstr(err, "no such table")) GUI_ERROR("prepare failed: %s — %s", sql, err);
if (strstr(err, "no such table")) GUI_DEBUG("optional table unavailable: %s — %s", sql, err);
else GUI_ERROR("prepare failed: %s — %s", sql, err);
sqlite3_finalize(stmt);
return nullptr;
}
if (!sqlite3_stmt_readonly(stmt)) {
GUI_ERROR("GUI write rejected: %s", sql); sqlite3_finalize(stmt); return nullptr;
}
return stmt;
}
/* Ошибки bind/step/finalize не превращаются в частичный успешный снимок. */
static bool finishRead(sqlite3_stmt* statement, int result) {
sqlite3* db = sqlite3_db_handle(statement);
const QByteArray sql = sqlite3_sql(statement);
const bool ok = result == SQLITE_ROW || result == SQLITE_DONE;
if (!ok) GUI_ERROR("read failed: rc=%d sql=%s error=%s", result, sql.constData(), sqlite3_errmsg(db));
const int final = sqlite3_finalize(statement);
if (final != SQLITE_OK) GUI_ERROR("read finalize failed: rc=%d sql=%s error=%s", final, sql.constData(), sqlite3_errmsg(db));
return ok && final == SQLITE_OK;
}
QString DbManager::colText(sqlite3_stmt* stmt, int col) {
return QString::fromUtf8(
reinterpret_cast<const char*>(sqlite3_column_text(stmt, col)));
@@ -396,19 +402,6 @@ QMap<quint64, QString> DbManager::getLocalNicknames(const QString& chId) const {
return m;
}
void DbManager::cleanOrphanedPeers(const QString& chId) const {
if (!m_db) return;
QString sql = QStringLiteral(
"DELETE FROM \"%1\" WHERE node_id NOT IN (SELECT node_id FROM nodes)"
).arg(peersTableName(chId));
char* err = nullptr;
int rc = sqlite3_exec(m_db, sql.toUtf8().constData(), nullptr, nullptr, &err);
if (rc != SQLITE_OK) {
qWarning("cleanOrphanedPeers: %s", err ? err : "?");
sqlite3_free(err);
}
}
QString DbManager::getDisplayName(quint64 nodeId) const {
sqlite3_stmt* st = prepareOrNull(
"SELECT display_name FROM accounts WHERE node_id=?");
@@ -432,60 +425,6 @@ QString DbManager::getDisplayName(quint64 nodeId) const {
return QString("node_%1").arg(nodeId);
}
QByteArray DbManager::loadNodeInfo(quint64 nodeId) const {
/* binary format: [x25519:32][ed25519:32][addr_count:1]([family:1][proto:1][addr_len:1][addr:4|16][port:2][rtt:2])... */
sqlite3_stmt* stmt = prepareOrNull(
"SELECT x25519_pubkey, ed25519_pubkey FROM nodes WHERE node_id=?");
if (!stmt) return {};
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return {}; }
QByteArray x25519 = colBlob(stmt, 0);
QByteArray ed25519 = colBlob(stmt, 1);
sqlite3_finalize(stmt);
QByteArray result;
result.append(x25519.isEmpty() ? QByteArray(32, '\0') : x25519);
result.append(ed25519.isEmpty() ? QByteArray(32, '\0') : ed25519);
/* запросить адреса */
stmt = prepareOrNull(
"SELECT family, protocol, address, port, rtt FROM node_addresses WHERE node_id=?");
if (!stmt) { result.append('\0'); return result; }
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
QByteArray addrsPart;
uint8_t cnt = 0;
while (sqlite3_step(stmt) == SQLITE_ROW && cnt < 255) {
int family = sqlite3_column_int(stmt, 0);
int proto = sqlite3_column_int(stmt, 1);
QByteArray addr = colBlob(stmt, 2);
uint16_t port = (uint16_t)sqlite3_column_int(stmt, 3);
int16_t rtt = (int16_t)(sqlite3_column_int(stmt, 4));
addrsPart.append((char)family);
addrsPart.append((char)proto);
addrsPart.append((char)addr.size());
addrsPart.append(addr);
addrsPart.append((const char*)&port, 2);
addrsPart.append((const char*)&rtt, 2);
cnt++;
}
sqlite3_finalize(stmt);
result.append((char)cnt);
result.append(addrsPart);
return result;
}
QList<quint64> DbManager::getOnlinePeers() const {
QList<quint64> peers;
/* без is_online: получаем всех кроме себя */
sqlite3_stmt* stmt = prepareOrNull("SELECT node_id FROM nodes WHERE node_id != ?");
if (!stmt) return peers;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)m_myNodeId);
while (sqlite3_step(stmt) == SQLITE_ROW)
peers.append((quint64)sqlite3_column_int64(stmt, 0));
sqlite3_finalize(stmt);
return peers;
}
QByteArray DbManager::getNodeEdPub(quint64 nodeId) const {
sqlite3_stmt* stmt = prepareOrNull("SELECT ed25519_pubkey FROM nodes WHERE node_id=?");
if (!stmt) return {};
@@ -496,30 +435,6 @@ QByteArray DbManager::getNodeEdPub(quint64 nodeId) const {
return r;
}
QList<NodeAddr> DbManager::getOnlinePeerAddresses(quint64 excludeNodeId) const {
QList<NodeAddr> list;
sqlite3_stmt* stmt = prepareOrNull(
"SELECT na.node_id, na.family, na.protocol, na.address, na.port, na.rtt, na.addr_type"
" FROM node_addresses na JOIN nodes n ON na.node_id = n.node_id"
" WHERE na.addr_type=0 AND na.node_id != ?"
" ORDER BY RANDOM()");
if (!stmt) return list;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)excludeNodeId);
while (sqlite3_step(stmt) == SQLITE_ROW) {
NodeAddr a;
a.nodeId = (quint64)sqlite3_column_int64(stmt, 0);
a.family = sqlite3_column_int(stmt, 1);
a.protocol = sqlite3_column_int(stmt, 2);
a.address = colBlob(stmt, 3);
a.port = (quint16)sqlite3_column_int(stmt, 4);
a.rtt = sqlite3_column_int(stmt, 5);
a.addrType = sqlite3_column_int(stmt, 6);
list.append(a);
}
sqlite3_finalize(stmt);
return list;
}
/* ── accounts ── */
QList<AccountRow> DbManager::getAccounts(bool contactsOnly) const {
@@ -602,102 +517,6 @@ int DbManager::getUnreadCount(const QString& channelId, qint64 lastReadMsgId) co
return count;
}
/* ── sync operations (read-only, for gui_bridge) ── */
QByteArray DbManager::syncListChannels() const {
/* binary: [count:2]([chId_len:1][chId:var])... */
QList<ChannelRow> chs = getChannels();
QByteArray out;
uint16_t cnt = (uint16_t)chs.size();
out.append((const char*)&cnt, 2);
for (auto& ch : chs) {
QByteArray id = ch.channelId.toUtf8();
out.append((char)(uint8_t)id.size());
out.append(id);
}
return out;
}
QByteArray DbManager::syncCount(const QString& chId) const {
QString sql = QStringLiteral("SELECT COUNT(*) FROM \"%1\"").arg(msgTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return {};
uint32_t cnt = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) cnt = (uint32_t)sqlite3_column_int64(stmt, 0);
sqlite3_finalize(stmt);
QByteArray out; out.append((const char*)&cnt, 4); return out;
}
QByteArray DbManager::syncCursorOpen(const QString& chId) {
QString sql = QStringLiteral(
"SELECT timestamp, node_id, data, author_signature"
" FROM \"%1\" ORDER BY timestamp, author_signature ASC").arg(msgTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) { QByteArray r(4, '\0'); return r; }
uint32_t id = m_nextCursorId++;
m_cursors[id] = stmt;
QByteArray r; r.append((const char*)&id, 4); return r;
}
QByteArray DbManager::syncCursorNext(uint32_t cursorId) {
auto it = m_cursors.find(cursorId);
if (it == m_cursors.end()) return {};
if (sqlite3_step(it.value()) != SQLITE_ROW) return {};
/* return: [ts:8][dh:8][nid:8][ct_len:1][ct:var][data_len:4][data:var] */
qint64 ts = sqlite3_column_int64(it.value(), 0);
uint64_t nid = (uint64_t)sqlite3_column_int64(it.value(), 1);
QByteArray jdata = colBlob(it.value(), 2);
/* parse JSON from data column */
auto jField = [](const QByteArray& j, const char* k) -> QByteArray {
QByteArray p = "\"" + QByteArray(k) + "\":\"";
int i = j.indexOf(p); if (i < 0) return {};
i += p.size(); int e = i;
while (e < j.size() && !(j[e] == '"' && (e == i || j[e - 1] != '\\'))) e++;
return (e > i) ? QByteArray(j.constData() + i, e - i) : QByteArray();
};
QByteArray ct_raw = jField(jdata, "ct");
QString ct = QString::fromUtf8(ct_raw);
if (ct.isEmpty()) ct = "text/plain";
QByteArray ddata = jField(jdata, "d");
/* compute datahash for wire format compat */
uint8_t dh_buf[32]; SHA256((const uint8_t*)ddata.constData(), ddata.size(), dh_buf);
uint64_t dh; memcpy(&dh, dh_buf, 8);
QByteArray out;
out.append((const char*)&ts, 8);
out.append((const char*)&dh, 8);
out.append((const char*)&nid, 8);
QByteArray ctb = ct.toUtf8();
out.append((char)(uint8_t)ctb.size());
out.append(ctb);
uint32_t dLen = (uint32_t)ddata.size();
out.append((const char*)&dLen, 4);
out.append(ddata);
return out;
}
void DbManager::syncCursorClose(uint32_t cursorId) {
auto it = m_cursors.find(cursorId);
if (it != m_cursors.end()) { sqlite3_finalize(it.value()); m_cursors.erase(it); }
}
QByteArray DbManager::syncListPeers(const QString& chId) const {
/* binary: [count:2]([node_id:8])... */
QString sql = QStringLiteral("SELECT node_id FROM \"%1\"").arg(peersTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) { QByteArray r(2, '\0'); return r; }
QList<uint64_t> ids;
while (sqlite3_step(stmt) == SQLITE_ROW)
ids.append((uint64_t)sqlite3_column_int64(stmt, 0));
sqlite3_finalize(stmt);
QByteArray out;
uint16_t cnt = (uint16_t)ids.size();
out.append((const char*)&cnt, 2);
for (auto id : ids) out.append((const char*)&id, 8);
return out;
}
/* ── integrity checks ── */
enum class PragmaMode { IntegrityCheck, Command };
+2 -30
View File
@@ -1,6 +1,6 @@
/* SQLite-представление данных для desktop UI: каналы/сообщения/узлы, локальные имена и диагностика БД.
* setDb заимствует handle ядра, destructor закрывает свои statement-курсоры, но не SQLite.
* Перед уничтожением БД закрыть курсоры и сбросить ссылку. Сетевые и подписанные изменения идут через chat_core;
* setDb заимствует handle ядра; каждый запрос закрывает свой statement до возврата, SQLite остаётся ядру.
* Перед уничтожением БД сбросить ссылку. Сетевые и подписанные изменения идут через chat_core;
* методы чтения возвращают самостоятельные Qt-значения. */
#ifndef DB_MANAGER_H
#define DB_MANAGER_H
@@ -58,17 +58,6 @@ struct AccountRow {
bool isContact = true;
};
struct NodeAddr {
quint64 nodeId = 0;
int family = 0;
int protocol = 0; // битмаска: 1=UDP, 2=TCP, 4=REALITY (6=TCP+REALITY)
QByteArray address;
quint16 port = 0;
int rtt = 0;
int addrType = 0; // ADDR_TYPE_*
int socketId = 0; // socket_id из ETCP
};
/* Результат синхронной проверки/обслуживания SQLite; успех optimize не подтверждает целостность данных. */
struct IntegrityResult {
bool ok = false; // Проверка: SQL успешен и получен ok без нарушений; optimize: SQL завершён успешно.
@@ -124,16 +113,12 @@ public:
QList<DmConversation> getDmConversations() const;
/* ── Участники канала (UI) ── */
void cleanOrphanedPeers(const QString& chId) const;
QString getDisplayName(quint64 nodeId) const;
QMap<quint64, QString> getLocalNicknames(const QString& chId) const;
bool isChannelMember(const QString& chId, quint64 nodeId) const;
/* ── Узлы ── */
QByteArray loadNodeInfo(quint64 nodeId) const;
QList<quint64> getOnlinePeers() const;
QByteArray getNodeEdPub(quint64 nodeId) const;
QList<NodeAddr> getOnlinePeerAddresses(quint64 excludeNodeId) const;
/* ── Аккаунты ── */
QList<AccountRow> getAccounts(bool contactsOnly = false) const;
@@ -143,15 +128,6 @@ public:
QString getUiState(const QString& key) const;
int getUiStateInt(const QString& key, int defaultVal) const;
/* ── Sync-операции (read-only, для gui_bridge/inline) ── */
QByteArray syncListChannels() const;
QByteArray syncCount(const QString& chId) const;
QByteArray syncCursorOpen(const QString& chId);
QByteArray syncCursorNext(uint32_t cursorId);
void syncCursorClose(uint32_t cursorId);
QByteArray syncListPeers(const QString& chId) const;
sqlite3_stmt* prepareOrNull(const char* sql) const;
static QString colText(sqlite3_stmt* stmt, int col);
static QByteArray colBlob(sqlite3_stmt* stmt, int col);
@@ -159,7 +135,6 @@ public:
QString msgTableName(const QString& chId) const;
QString peersTableName(const QString& chId) const;
static QString sanitizeTableName(const QString& chId);
static uint64_t computeDatahash(const QByteArray& data);
/* Синхронные PRAGMA на заимствованном handle; вызывающий сохраняет его живым до возврата, ошибки логируются в CONFIG. */
IntegrityResult integrityCheckQuick() const; // quick_check: требует ответ ok, нарушения в строках SQLite означают отказ.
@@ -170,9 +145,6 @@ public:
QString m_dbPath;
quint64 m_myNodeId = 0;
/* Кэш открытых курсоров для sync-операций */
mutable QMap<uint32_t, sqlite3_stmt*> m_cursors;
mutable uint32_t m_nextCursorId = 1;
};
#endif