From 371208d4897d8ab186753c9d39a6f44ee9dba162 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 15 Jul 2026 14:26:32 +0300 Subject: [PATCH] =?UTF-8?q?chatgui:=20=D0=B2=D1=8B=D0=BF=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B2=D1=81=D0=B5=20=D0=B7=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B8=20=D0=B2=20=D0=91=D0=94=20=D0=B8=D0=B7=20GUI=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=82=D0=BE=D0=BA=D0=B0=20(DbManager)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DbManager теперь read-only, получает sqlite3* handle через setDb() - chat_core — единственный владелец БД (SQLITE_OPEN_FULLMUTEX + WAL) - initTables/seedStubData/createChannel/insertMessage/setUiState удалены из DbManager - seed 5 stub-аккаунтов перенесён в chat_core_init() - GUI_EVT_DB_READY — уведомление о готовности БД - setUiState заменён на QSettings - каналы загружаются по DB_READY коллбэку - убран двойной sqlite3_open (один файл, один хендл) --- tools/chatgui/db/db_manager.cpp | 317 +------------------- tools/chatgui/db/db_manager.h | 35 +-- tools/chatgui/src/main.cpp | 2 +- tools/chatgui/src/mainwindow.cpp | 30 +- tools/chatgui/src/mainwindow.h | 1 + tools/chatgui/transport/chat_core.c | 40 ++- tools/chatgui/transport/chat_core.h | 2 + tools/chatgui/transport/gui_bridge.h | 5 + tools/chatgui/transport/gui_bridge_impl.cpp | 9 + 9 files changed, 82 insertions(+), 359 deletions(-) diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp index 40a3286f..4b2520e9 100644 --- a/tools/chatgui/db/db_manager.cpp +++ b/tools/chatgui/db/db_manager.cpp @@ -7,8 +7,6 @@ /* ── helpers ── */ -static const char kZero64[64] = {}; - QString DbManager::sanitizeTableName(const QString& chId) { QString s; s.reserve(chId.size()); @@ -59,36 +57,15 @@ QByteArray DbManager::getLastChainHash(const QString& chId) const { /* ── lifecycle ── */ -DbManager::DbManager(const QString& path, QObject* parent) +DbManager::DbManager(QObject* parent) : QObject(parent) { - int rc = sqlite3_open(path.toUtf8().constData(), &m_db); - if (rc != SQLITE_OK) { - GUI_ERROR("Cannot open database: %s", sqlite3_errmsg(m_db)); - sqlite3_close(m_db); m_db = nullptr; return; - } - sqlite3_exec(m_db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr); - sqlite3_exec(m_db, "PRAGMA foreign_keys=ON", nullptr, nullptr, nullptr); - - initTables(); - - m_myNodeId = 100; - sqlite3_stmt* st = prepareOrNull("SELECT node_id FROM local_identity WHERE id=1"); - if (st) { - if (sqlite3_step(st) == SQLITE_ROW) m_myNodeId = (quint64)sqlite3_column_int64(st, 0); - sqlite3_finalize(st); - } - - if (getChannels().isEmpty()) seedStubData(); - GUI_INFO("Database opened: %s (my_node_id=%llu)", path.toUtf8().constData(), - (unsigned long long)m_myNodeId); } DbManager::~DbManager() { for (auto it = m_cursors.begin(); it != m_cursors.end(); ++it) sqlite3_finalize(it.value()); m_cursors.clear(); - if (m_db) { sqlite3_close(m_db); m_db = nullptr; } } sqlite3_stmt* DbManager::prepareOrNull(const char* sql) const { @@ -112,70 +89,6 @@ QByteArray DbManager::colBlob(sqlite3_stmt* stmt, int col) { return QByteArray(static_cast(sqlite3_column_blob(stmt, col)), len); } -/* ── init ── */ - -void DbManager::initTables() { - const char* sql = - "CREATE TABLE IF NOT EXISTS local_identity (" - " id INTEGER PRIMARY KEY CHECK (id = 1)," - " node_id INTEGER NOT NULL UNIQUE," - " name TEXT NOT NULL," - " x25519_pubkey BLOB NOT NULL," - " x25519_privkey BLOB," - " ed25519_pubkey BLOB," - " created_at INTEGER DEFAULT (unixepoch())," - " updated_at INTEGER DEFAULT (unixepoch())" - ");" - - "CREATE TABLE IF NOT EXISTS accounts (" - " node_id INTEGER PRIMARY KEY," - " display_name TEXT NOT NULL," - " avatar_color TEXT DEFAULT '#4A90E2'," - " avatar_letter TEXT NOT NULL," - " is_contact INTEGER DEFAULT 1," - " created_at INTEGER DEFAULT (unixepoch())" - ");" - - "CREATE TABLE IF NOT EXISTS ui_state (" - " key TEXT PRIMARY KEY," - " value TEXT" - ");" - - /* таблицы создаваемые uTun-потоком — дублируем IF NOT EXISTS чтобы GUI мог читать независимо */ - "CREATE TABLE IF NOT EXISTS nodes (" - " node_id INTEGER PRIMARY KEY," - " name TEXT," - " x25519_pubkey BLOB NOT NULL," - " ed25519_pubkey BLOB," - " last_seen_at INTEGER," - " online INTEGER DEFAULT 0," - " created_at INTEGER DEFAULT (unixepoch())" - ");" - - "CREATE TABLE IF NOT EXISTS channels (" - " channel_id TEXT PRIMARY KEY," - " name TEXT NOT NULL," - " owner_node_id INTEGER," - " is_dm INTEGER DEFAULT 0," - " x25519_pubkey BLOB NOT NULL," - " x25519_privkey BLOB," - " ed25519_pubkey BLOB NOT NULL," - " ed25519_privkey BLOB," - " signature BLOB NOT NULL," - " last_read_msg_id INTEGER," - " last_pos_msg_id INTEGER," - " created_at INTEGER DEFAULT (unixepoch())" - ");"; - - char* err = nullptr; - if (sqlite3_exec(m_db, sql, nullptr, nullptr, &err) != SQLITE_OK) { - GUI_ERROR("initTables: %s", err); - sqlite3_free(err); - } else { - GUI_INFO("Database tables initialized"); - } -} - /* ── channels ── */ QList DbManager::getChannels() const { @@ -206,50 +119,6 @@ QList DbManager::getChannels() const { return list; } -bool DbManager::createChannel(const QString& chId, const QString& name, - quint64 ownerNodeId, - const QByteArray& x25519pub, const QByteArray& x25519priv, - const QByteArray& ed25519pub, const QByteArray& ed25519priv) { - GUI_INFO("createChannel (msg table only): chId='%s' name='%s'", chId.toUtf8().constData(), name.toUtf8().constData()); - (void)ownerNodeId; (void)x25519pub; (void)x25519priv; (void)ed25519pub; (void)ed25519priv; - - QString msgt = msgTableName(chId); - - QString csql = QStringLiteral( - "CREATE TABLE IF NOT EXISTS \"%1\" (" - " id INTEGER PRIMARY KEY AUTOINCREMENT," - " node_id INTEGER NOT NULL," - " content_type TEXT NOT NULL," - " data BLOB NOT NULL," - " timestamp INTEGER NOT NULL," - " datahash INTEGER NOT NULL," - " chain_hash BLOB NOT NULL," - " signature BLOB," - " is_outgoing INTEGER DEFAULT 0," - " is_read INTEGER DEFAULT 0," - " sync_flags INTEGER DEFAULT 0," - " UNIQUE(timestamp, datahash))").arg(msgt); - sqlite3_exec(m_db, csql.toUtf8().constData(), nullptr, nullptr, nullptr); - csql = QStringLiteral("CREATE INDEX IF NOT EXISTS idx_%1_ts ON \"%1\"(timestamp, datahash)").arg(msgt); - sqlite3_exec(m_db, csql.toUtf8().constData(), nullptr, nullptr, nullptr); - - GUI_INFO("Message table ensured: %s", msgt.toUtf8().constData()); - return true; -} - -bool DbManager::deleteChannel(const QString& chId) { - QString msgt = msgTableName(chId), pt = peersTableName(chId); - sqlite3_exec(m_db, QStringLiteral("DROP TABLE IF EXISTS \"%1\"").arg(msgt).toUtf8().constData(), nullptr, nullptr, nullptr); - sqlite3_exec(m_db, QStringLiteral("DROP TABLE IF EXISTS \"%1\"").arg(pt).toUtf8().constData(), nullptr, nullptr, nullptr); - sqlite3_stmt* stmt = prepareOrNull("DELETE FROM channels WHERE channel_id=?"); - if (!stmt) return false; - sqlite3_bind_text(stmt, 1, chId.toUtf8().constData(), -1, SQLITE_TRANSIENT); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - GUI_INFO("Channel deleted: %s", chId.toUtf8().constData()); - return true; -} - /* ── messages (UI) ── */ QList DbManager::getMessages(const QString& chId, int limit) const { @@ -279,44 +148,6 @@ QList DbManager::getMessages(const QString& chId, int limit) const { return list; } -bool DbManager::insertMessage(const QString& chId, quint64 nodeId, - const QString& contentType, const QByteArray& data, - qint64 timestamp, bool isOutgoing, - uint64_t* outDatahash, QByteArray* outChainHash) { - QString tbl = msgTableName(chId); - - uint64_t dh = computeDatahash(data); - QByteArray prevCh = getLastChainHash(chId); - QByteArray ch = computeChainHash(prevCh, timestamp, dh); - - QString sql = QStringLiteral( - "INSERT OR IGNORE INTO \"%1\"" - " (node_id, content_type, data, timestamp, datahash, chain_hash," - " signature, is_outgoing, is_read)" - " VALUES(?,?,?,?,?,?,?,?,1)").arg(tbl); - sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData()); - if (!stmt) { - qWarning("DbManager: insertMessage prepare failed: %s", sqlite3_errmsg(m_db)); - return false; - } - sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId); - sqlite3_bind_text(stmt, 2, contentType.toUtf8().constData(), -1, SQLITE_TRANSIENT); - sqlite3_bind_blob(stmt, 3, data.constData(), data.size(), SQLITE_TRANSIENT); - sqlite3_bind_int64(stmt, 4, timestamp); - sqlite3_bind_int64(stmt, 5, (sqlite3_int64)dh); - sqlite3_bind_blob(stmt, 6, ch.constData(), 32, SQLITE_TRANSIENT); - sqlite3_bind_blob(stmt, 7, kZero64, 64, SQLITE_TRANSIENT); - sqlite3_bind_int(stmt, 8, isOutgoing ? 1 : 0); - bool ok = sqlite3_step(stmt) == SQLITE_DONE; - sqlite3_finalize(stmt); - - if (ok) { - if (outDatahash) *outDatahash = dh; - if (outChainHash) *outChainHash = ch; - } - return ok; -} - /* ── peers (UI) ── */ int DbManager::getChannelMemberCount(const QString& chId) const { @@ -374,20 +205,6 @@ QString DbManager::getDisplayName(quint64 nodeId) const { return QString("node_%1").arg(nodeId); } -/* ── nodes (запись теперь через topo_node_sqlite) ── */ - -bool DbManager::upsertNode(quint64 nodeId, const QString& name, - const QByteArray& x25519pub, const QByteArray& ed25519pub) { - (void)nodeId; (void)name; (void)x25519pub; (void)ed25519pub; - return true; -} - -bool DbManager::addNodeAddress(quint64 nodeId, int family, int protocol, - const QByteArray& addr, quint16 port, bool isNat) { - (void)nodeId; (void)family; (void)protocol; (void)addr; (void)port; (void)isNat; - return true; -} - 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( @@ -530,16 +347,7 @@ QString DbManager::getUiState(const QString& key) const { return val; } -void DbManager::setUiState(const QString& key, const QString& value) { - sqlite3_stmt* stmt = prepareOrNull( - "INSERT OR REPLACE INTO ui_state(key,value) VALUES(?,?)"); - if (!stmt) return; - sqlite3_bind_text(stmt, 1, key.toUtf8().constData(), -1, SQLITE_TRANSIENT); - sqlite3_bind_text(stmt, 2, value.toUtf8().constData(), -1, SQLITE_TRANSIENT); - sqlite3_step(stmt); sqlite3_finalize(stmt); -} - -/* ── sync operations (called from gui_bridge, in GUI thread) ── */ +/* ── sync operations (read-only, for gui_bridge) ── */ QByteArray DbManager::syncListChannels() const { /* binary: [count:2]([chId_len:1][chId:var])... */ @@ -578,55 +386,6 @@ QByteArray DbManager::syncChainHashAt(const QString& chId, uint32_t pos) const { return r.size() == 32 ? r : QByteArray(32, '\0'); } -QByteArray DbManager::syncInsert(const QString& chId, const QByteArray& recData) { - /* recData: [timestamp:8][datahash:8][node_id:8][ct_len:1][ct:var][data_len:4][data:var][chain_hash:32] */ - if (recData.size() < 29) return QByteArray(1, (char)-1); - - const uint8_t* p = (const uint8_t*)recData.constData(); - int rem = recData.size(); - qint64 ts; uint64_t dh, nid; - memcpy(&ts, p, 8); p += 8; rem -= 8; - memcpy(&dh, p, 8); p += 8; rem -= 8; - memcpy(&nid, p, 8); p += 8; rem -= 8; - if (rem < 1) return QByteArray(1, (char)-1); - uint8_t ctLen = *p; p++; rem--; - if (rem < ctLen) return QByteArray(1, (char)-1); - QString ct = QString::fromUtf8((const char*)p, ctLen); p += ctLen; rem -= ctLen; - if (rem < 4) return QByteArray(1, (char)-1); - uint32_t dLen; memcpy(&dLen, p, 4); p += 4; rem -= 4; - if (rem < (int)dLen) return QByteArray(1, (char)-1); - QByteArray data((const char*)p, dLen); p += dLen; rem -= dLen; - if (rem < 32) return QByteArray(1, (char)-1); - QByteArray peerCH((const char*)p, 32); - - /* verify chain_hash */ - QByteArray prev = getLastChainHash(chId); - QByteArray expected = computeChainHash(prev, ts, dh); - if (expected != peerCH) { - GUI_ERROR("syncInsert: chain_hash mismatch for %s ts=%lld dh=%llx", chId.toUtf8().constData(), (long long)ts, (unsigned long long)dh); - return QByteArray(1, (char)-1); - } - - QString sql = QStringLiteral( - "INSERT OR IGNORE INTO \"%1\"" - " (node_id, content_type, data, timestamp, datahash, chain_hash, signature, is_outgoing, is_read)" - " VALUES(?,?,?,?,?,?,?,0,1)").arg(msgTableName(chId)); - sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData()); - if (!stmt) return QByteArray(1, (char)-1); - sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nid); - sqlite3_bind_text(stmt, 2, ct.toUtf8().constData(), -1, SQLITE_TRANSIENT); - sqlite3_bind_blob(stmt, 3, data.constData(), data.size(), SQLITE_TRANSIENT); - sqlite3_bind_int64(stmt, 4, ts); - sqlite3_bind_int64(stmt, 5, (sqlite3_int64)dh); - sqlite3_bind_blob(stmt, 6, peerCH.constData(), 32, SQLITE_TRANSIENT); - sqlite3_bind_blob(stmt, 7, kZero64, 64, SQLITE_TRANSIENT); - int rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - if (rc == SQLITE_DONE) return QByteArray(1, '\0'); - if (rc == SQLITE_CONSTRAINT) return QByteArray(1, (char)1); /* duplicate */ - return QByteArray(1, (char)-1); -} - QByteArray DbManager::syncCursorOpen(const QString& chId) { QString sql = QStringLiteral( "SELECT timestamp, datahash, node_id, content_type, data" @@ -668,32 +427,6 @@ void DbManager::syncCursorClose(uint32_t cursorId) { if (it != m_cursors.end()) { sqlite3_finalize(it.value()); m_cursors.erase(it); } } -QByteArray DbManager::syncMarkSent(const QString& chId, quint64 ts, quint64 dh, quint64 myNodeId) { - QString sql = QStringLiteral( - "UPDATE \"%1\" SET sync_flags=sync_flags|1" - " WHERE timestamp=? AND datahash=? AND node_id=? AND is_outgoing=1") - .arg(msgTableName(chId)); - sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData()); - if (!stmt) return {}; - sqlite3_bind_int64(stmt, 1, (sqlite3_int64)ts); - sqlite3_bind_int64(stmt, 2, (sqlite3_int64)dh); - sqlite3_bind_int64(stmt, 3, (sqlite3_int64)myNodeId); - sqlite3_step(stmt); sqlite3_finalize(stmt); - return {}; -} - -QByteArray DbManager::syncTtlDelete(const QString& chId, quint64 myNodeId, quint64 cutoffUs) { - QString sql = QStringLiteral( - "DELETE FROM \"%1\" WHERE node_id=? AND is_outgoing=1 AND (sync_flags&1)=0 AND timestamp getChannels() const; - bool createChannel(const QString& chId, const QString& name, - quint64 ownerNodeId = 0, - const QByteArray& x25519pub = {}, const QByteArray& x25519priv = {}, - const QByteArray& ed25519pub = {}, const QByteArray& ed25519priv = {}); - bool deleteChannel(const QString& chId); /* ── Сообщения (UI) ── */ QList getMessages(const QString& chId, int limit = 50) const; - bool insertMessage(const QString& chId, quint64 nodeId, - const QString& contentType, const QByteArray& data, - qint64 timestamp, bool isOutgoing, - uint64_t* outDatahash, QByteArray* outChainHash); /* ── Участники канала (UI) ── */ int getChannelMemberCount(const QString& chId) const; @@ -93,10 +85,6 @@ public: QString getDisplayName(quint64 nodeId) const; /* ── Узлы ── */ - bool upsertNode(quint64 nodeId, const QString& name, - const QByteArray& x25519pub, const QByteArray& ed25519pub); - bool addNodeAddress(quint64 nodeId, int family, int protocol, - const QByteArray& addr, quint16 port, bool isNat); QByteArray loadNodeInfo(quint64 nodeId) const; QList getOnlinePeers() const; QByteArray getNodeEdPub(quint64 nodeId) const; @@ -106,33 +94,19 @@ public: QList getAccounts(bool contactsOnly = false) const; AccountRow getAccount(quint64 nodeId) const; - /* ── UI state ── */ + /* ── UI state (read-only используем QSettings, оставлен для совместимости) ── */ QString getUiState(const QString& key) const; - void setUiState(const QString& key, const QString& value); - /* ── Sync-операции (вызываются gui_bridge, в GUI-потоке) ── */ + /* ── Sync-операции (read-only, для gui_bridge/inline) ── */ QByteArray syncListChannels() const; QByteArray syncCount(const QString& chId) const; QByteArray syncChainHashAt(const QString& chId, uint32_t pos) const; - QByteArray syncInsert(const QString& chId, const QByteArray& recData); QByteArray syncCursorOpen(const QString& chId); QByteArray syncCursorNext(uint32_t cursorId); void syncCursorClose(uint32_t cursorId); - QByteArray syncMarkSent(const QString& chId, quint64 ts, quint64 dh, quint64 myNodeId); - QByteArray syncTtlDelete(const QString& chId, quint64 myNodeId, quint64 cutoffUs); QByteArray syncListPeers(const QString& chId) const; - /* ── Уведомления из uasync ── */ - void onSyncMessageReceived(const QString& chId); - void setNodeOnline(quint64 nodeId, bool online); - void setUasyncInstance(void* ua) { m_ua = ua; } - void* uasyncInstance() const { return m_ua; } - /* ── seed ── */ - void seedStubData(); - -private: - void initTables(); sqlite3_stmt* prepareOrNull(const char* sql) const; static QString colText(sqlite3_stmt* stmt, int col); static QByteArray colBlob(sqlite3_stmt* stmt, int col); @@ -147,7 +121,6 @@ private: sqlite3* m_db = nullptr; quint64 m_myNodeId = 0; - void* m_ua = nullptr; /* Кэш открытых курсоров для sync-операций */ mutable QMap m_cursors; diff --git a/tools/chatgui/src/main.cpp b/tools/chatgui/src/main.cpp index cc77851d..bf806583 100644 --- a/tools/chatgui/src/main.cpp +++ b/tools/chatgui/src/main.cpp @@ -70,7 +70,7 @@ int main(int argc, char *argv[]) { if (!QDir::isAbsolutePath(dbPath)) dbPath = QCoreApplication::applicationDirPath() + "/" + dbPath; - DbManager db(dbPath); + DbManager db; MainWindow w(nullptr, &db, cfgPath, dbPath, cfg.debugFile(), cfg.debugLevel(), cfg.debugCategories()); w.show(); diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index adce67e0..4f2bab4e 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -16,6 +16,7 @@ #include #include #include +#include extern "C" { #include "../transport/chat_core.h" @@ -78,7 +79,7 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath, setupUi(); setupTray(); - if (m_db && m_db->isOpen()) { + if (m_db) { s_mainWindow = this; gui_bridge_set_msg_received_cb(onMsgReceivedCallback); gui_bridge_set_channel_updated_cb(onChannelUpdatedCallback); @@ -89,13 +90,25 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath, }); gui_bridge_set_auto_connect_status_cb(onAutoConnectStatusCallback); gui_bridge_set_channel_peers_online_cb(onChannelPeersOnlineCallback); + gui_bridge_set_db_ready_cb([]() { + if (!s_mainWindow || !s_mainWindow->m_db) return; + s_mainWindow->m_db->setDb(chat_core_get_db()); + s_mainWindow->m_channelList->loadChannels(); + + QString lastCid = QSettings().value("last_channel_id").toString(); + if (!lastCid.isEmpty()) { + s_mainWindow->m_channelList->selectChannel(lastCid); + s_mainWindow->m_accountList->setChannel(lastCid); + } + }); setupNode(); setupMessaging(); connect(m_channelList, &ChannelList::channelSelected, this, [this](const QString& channelId) { - m_db->setUiState("last_channel_id", channelId); + QSettings().setValue("last_channel_id", channelId); + m_currentChannelId = channelId; m_messageList->loadChannel(channelId); m_accountList->setChannel(channelId); }); @@ -108,14 +121,6 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath, this, &MainWindow::onCreateGroupRequested); connect(m_channelList, &ChannelList::settingsRequested, this, &MainWindow::showSettings); - - m_channelList->loadChannels(); - - QString lastCid = m_db->getUiState("last_channel_id"); - if (!lastCid.isEmpty()) { - m_channelList->selectChannel(lastCid); - m_accountList->setChannel(lastCid); - } } } @@ -266,7 +271,7 @@ void MainWindow::onMessageReceived(const char* ch_id) { } void MainWindow::onMembersChanged(const char* ch_id) { - QString cur = m_db->getUiState("last_channel_id"); + QString cur = QSettings().value("last_channel_id").toString(); if (cur == QString::fromUtf8(ch_id)) m_accountList->refresh(); } @@ -292,7 +297,8 @@ void MainWindow::reloadChannels() { m_channelList->selectChannel(m_pendingChannelSelect); m_accountList->setChannel(m_pendingChannelSelect); m_messageList->loadChannel(m_pendingChannelSelect); - m_db->setUiState("last_channel_id", m_pendingChannelSelect); + QSettings().setValue("last_channel_id", m_pendingChannelSelect); + m_currentChannelId = m_pendingChannelSelect; m_pendingChannelSelect.clear(); } } diff --git a/tools/chatgui/src/mainwindow.h b/tools/chatgui/src/mainwindow.h index c5a70ed9..90f1efe2 100644 --- a/tools/chatgui/src/mainwindow.h +++ b/tools/chatgui/src/mainwindow.h @@ -56,4 +56,5 @@ private: QString m_cfgDebugLevel; QString m_cfgDebugCategories; QString m_pendingChannelSelect; + QString m_currentChannelId; }; diff --git a/tools/chatgui/transport/chat_core.c b/tools/chatgui/transport/chat_core.c index bb9583ef..a7bf899f 100644 --- a/tools/chatgui/transport/chat_core.c +++ b/tools/chatgui/transport/chat_core.c @@ -131,7 +131,8 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { if (!inst || !db_path) return -1; memset(&g_cc, 0, sizeof(g_cc)); - int rc = sqlite3_open(db_path, &g_cc.db); + int rc = sqlite3_open_v2(db_path, &g_cc.db, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL); if (rc != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTIVITY, "%s: cannot open DB %s: %s", CC_ID, db_path, sqlite3_errmsg(g_cc.db)); @@ -200,6 +201,36 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { ");" ); + /* seed stub accounts if empty */ + { + sqlite3_stmt* cnt_st = NULL; + if (sqlite3_prepare_v2(g_cc.db, "SELECT COUNT(*) FROM accounts", + -1, &cnt_st, NULL) == SQLITE_OK) { + if (sqlite3_step(cnt_st) == SQLITE_ROW && sqlite3_column_int(cnt_st, 0) == 0) { + DEBUG_INFO(DEBUG_CATEGORY_CONNECTIVITY, "%s: seeding 5 stub accounts", CC_ID); + struct { int64_t id; const char* name; const char* letter; const char* color; } accs[] = { + {1, "Alice", "A", "#4A90E2"}, {2, "Bob", "B", "#7ED321"}, + {3, "Carol", "C", "#F5A623"}, {4, "Dave", "D", "#9013FE"}, + {5, "Eve", "E", "#00B894"}, + }; + for (int i = 0; i < 5; i++) { + sqlite3_stmt* ins = NULL; + sqlite3_prepare_v2(g_cc.db, + "INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter)" + " VALUES(?,?,?,?)", -1, &ins, NULL); + if (ins) { + sqlite3_bind_int64(ins, 1, accs[i].id); + sqlite3_bind_text(ins, 2, accs[i].name, -1, SQLITE_STATIC); + sqlite3_bind_text(ins, 3, accs[i].color, -1, SQLITE_STATIC); + sqlite3_bind_text(ins, 4, accs[i].letter, -1, SQLITE_STATIC); + sqlite3_step(ins); sqlite3_finalize(ins); + } + } + } + sqlite3_finalize(cnt_st); + } + } + g_cc.initialized = 1; /* ensure infrastructure for all existing channels (survives restart) */ @@ -220,9 +251,16 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { { uint8_t nid[8]; memcpy(nid, &g_cc.my_node_id, 8); gui_bridge_post(GUI_EVT_MY_NODE_ID, nid, 8); } + /* notify GUI: DB is ready (tables created, data seeded) */ + gui_bridge_post(GUI_EVT_DB_READY, NULL, 0); + return 0; } +sqlite3* chat_core_get_db(void) { + return g_cc.db; +} + void chat_core_destroy(struct UTUN_INSTANCE* inst) { (void)inst; if (!g_cc.initialized) return; diff --git a/tools/chatgui/transport/chat_core.h b/tools/chatgui/transport/chat_core.h index 686dc900..abc1c730 100644 --- a/tools/chatgui/transport/chat_core.h +++ b/tools/chatgui/transport/chat_core.h @@ -19,6 +19,8 @@ struct UTUN_INSTANCE; int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path); void chat_core_destroy(struct UTUN_INSTANCE* inst); +struct sqlite3* chat_core_get_db(void); + /* ── Отправка сообщения (GUI → uasync) ── */ struct chat_msg_submit { diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index c2e5e675..c1c4d2bb 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -20,6 +20,7 @@ struct UASYNC; #define GUI_EVT_MY_NODE_ID 6 /* data: [node_id:8] */ #define GUI_EVT_AUTO_CONNECT_STATUS 7 /* data: [status:1][total_tried:2][node_count:2][success:2] */ #define GUI_EVT_CHANNEL_PEERS_ONLINE 8 /* data: [ch_id_len:1][ch_id:var][online_count:2] */ +#define GUI_EVT_DB_READY 9 /* data: none — DB is open, tables created */ /* ── API ── */ @@ -67,6 +68,10 @@ void gui_bridge_set_auto_connect_status_cb(gui_auto_connect_status_fn cb); typedef void (*gui_channel_peers_online_fn)(const char* ch_id, int ch_id_len, uint16_t online_count); void gui_bridge_set_channel_peers_online_cb(gui_channel_peers_online_fn cb); +/* Callback: DB открыта, таблицы созданы, можно загружать данные (вызывается из GUI-потока) */ +typedef void (*gui_db_ready_fn)(void); +void gui_bridge_set_db_ready_cb(gui_db_ready_fn cb); + #ifdef __cplusplus } #endif diff --git a/tools/chatgui/transport/gui_bridge_impl.cpp b/tools/chatgui/transport/gui_bridge_impl.cpp index 4c71d7c8..40ac7ec9 100644 --- a/tools/chatgui/transport/gui_bridge_impl.cpp +++ b/tools/chatgui/transport/gui_bridge_impl.cpp @@ -31,6 +31,7 @@ static gui_members_changed_fn g_members_changed_cb = nullptr; static gui_my_node_id_fn g_my_node_id_cb = nullptr; static gui_auto_connect_status_fn g_auto_connect_status_cb = nullptr; static gui_channel_peers_online_fn g_channel_peers_online_cb = nullptr; +static gui_db_ready_fn g_db_ready_cb = nullptr; static struct UASYNC* g_ua = nullptr; /* ── GuiBridgeReceiver implementation ── */ @@ -121,6 +122,10 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) { } } break; + case GUI_EVT_DB_READY: + DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "gui_bridge: DB_READY"); + if (g_db_ready_cb) g_db_ready_cb(); + break; default: DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "gui_bridge: unknown event type %d", eventType); break; @@ -195,6 +200,10 @@ void gui_bridge_set_channel_peers_online_cb(gui_channel_peers_online_fn cb) { g_channel_peers_online_cb = cb; } +void gui_bridge_set_db_ready_cb(gui_db_ready_fn cb) { + g_db_ready_cb = cb; +} + } /* extern "C" */ #include "gui_bridge_impl.moc"