Remove obsolete desktop DB sync and GUI writes and enforce read-only queries
This commit is contained in:
1 parent
f87ee63a1d
commit
ce133a6acf
2 files changed
+20
-229
No files matched your search
+18
-199
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user