chatgui: preserve active channel selection on reload, members sync, profile settings, member list model

This commit is contained in:
Evgeny committed 2026-07-14 08:52:10 +03:00
1 parent c3f946fb63
commit d70cdbb040
28 files changed
+1895 -91

No files matched your search

+97 -14
View File
@@ -69,8 +69,16 @@ DbManager::DbManager(const QString& path, QObject* parent)
}
sqlite3_exec(m_db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr);
sqlite3_exec(m_db, "PRAGMA foreign_keys=ON", nullptr, nullptr, nullptr);
m_myNodeId = 100;
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);
@@ -86,7 +94,8 @@ DbManager::~DbManager() {
sqlite3_stmt* DbManager::prepareOrNull(const char* sql) const {
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
GUI_ERROR("prepare failed: %s — %s", sql, sqlite3_errmsg(m_db));
const char* err = sqlite3_errmsg(m_db);
if (!strstr(err, "no such table")) GUI_ERROR("prepare failed: %s — %s", sql, err);
return nullptr;
}
return stmt;
@@ -119,7 +128,7 @@ void DbManager::initTables() {
");"
"CREATE TABLE IF NOT EXISTS accounts ("
" node_id INTEGER PRIMARY KEY REFERENCES nodes(node_id),"
" node_id INTEGER PRIMARY KEY,"
" display_name TEXT NOT NULL,"
" avatar_color TEXT DEFAULT '#4A90E2',"
" avatar_letter TEXT NOT NULL,"
@@ -130,6 +139,32 @@ void DbManager::initTables() {
"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;
@@ -248,6 +283,8 @@ 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);
@@ -256,9 +293,12 @@ bool DbManager::insertMessage(const QString& chId, quint64 nodeId,
"INSERT OR IGNORE INTO \"%1\""
" (node_id, content_type, data, timestamp, datahash, chain_hash,"
" signature, is_outgoing, is_read)"
" VALUES(?,?,?,?,?,?,?,?,1)").arg(msgTableName(chId));
" VALUES(?,?,?,?,?,?,?,?,1)").arg(tbl);
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return false;
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);
@@ -277,18 +317,61 @@ bool DbManager::insertMessage(const QString& chId, quint64 nodeId,
return ok;
}
/* ── peers (запись теперь через topo_node_sqlite) ── */
/* ── peers (UI) ── */
bool DbManager::addPeer(const QString& chId, quint64 nodeId,
const QByteArray& joinSig, const QByteArray& creatorSig,
const QString& comment) {
(void)chId; (void)nodeId; (void)joinSig; (void)creatorSig; (void)comment;
return true;
int DbManager::getChannelMemberCount(const QString& chId) const {
QString sql = QStringLiteral("SELECT COUNT(*) FROM \"%1\"").arg(peersTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return 0;
int c = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) c = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return c;
}
bool DbManager::removePeer(const QString& chId, quint64 nodeId) {
(void)chId; (void)nodeId;
return true;
QList<ChannelMember> DbManager::getChannelMembers(const QString& chId, int offset, int limit) const {
QList<ChannelMember> list;
QString sql = QStringLiteral(
"SELECT p.node_id, COALESCE(n.online,0), COALESCE(n.name,'')"
" FROM \"%1\" p LEFT JOIN nodes n ON p.node_id=n.node_id"
" ORDER BY n.online DESC, p.node_id ASC LIMIT ? OFFSET ?"
).arg(peersTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return list;
sqlite3_bind_int(stmt, 1, limit);
sqlite3_bind_int(stmt, 2, offset);
while (sqlite3_step(stmt) == SQLITE_ROW) {
ChannelMember m;
m.nodeId = (quint64)sqlite3_column_int64(stmt, 0);
m.online = sqlite3_column_int(stmt, 1) != 0;
m.name = colText(stmt, 2);
list.append(m);
}
sqlite3_finalize(stmt);
return list;
}
QString DbManager::getDisplayName(quint64 nodeId) const {
sqlite3_stmt* st = prepareOrNull(
"SELECT display_name FROM accounts WHERE node_id=?");
if (st) {
sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId);
if (sqlite3_step(st) == SQLITE_ROW) {
QString s = colText(st, 0);
sqlite3_finalize(st);
if (!s.isEmpty()) return s;
} else sqlite3_finalize(st);
}
st = prepareOrNull("SELECT name FROM nodes WHERE node_id=?");
if (st) {
sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId);
if (sqlite3_step(st) == SQLITE_ROW) {
QString s = colText(st, 0);
sqlite3_finalize(st);
if (!s.isEmpty()) return s;
} else sqlite3_finalize(st);
}
return QString("node_%1").arg(nodeId);
}
/* ── nodes (запись теперь через topo_node_sqlite) ── */