chatgui: create group, unified logging, fix uasync_post epoll bug

- Add CreateGroupDialog with group name input
- Generate X25519+Ed25519 keypairs, random uint64 channel_id,
  Ed25519 signature, write to DB via uasync thread
- chat_core_create_channel(): create msg table, topo_node_sqlite_channel_put()
- GUI_EVT_CHANNEL_UPDATED notification to reload channel list
- Per-category debug config: debug_level= + debug_categories= in [gui]
- Unified logging via debug_config: all threads write to same file/console
- debug_enable_console(1) to mirror file output to stderr
- DEBUG_LEVEL_DISABLED=-1: truly disable category, NONE=0 fallback to global
- Fix uasync_post under epoll: process_posted_tasks was never called
- Fix MainWindow constructor parameter ordering (dbPath vs debugFile)
- debug_ui.h: append mode ('a' instead of 'w')
This commit is contained in:
Evgeny committed 2026-07-12 17:39:20 +03:00
1 parent a9a6b6bb10
commit de7841f33a
26 files changed
+761 -265

No files matched your search

+19 -151
View File
@@ -8,7 +8,6 @@
/* ── helpers ── */
static const char kZero64[64] = {};
static const char kZero32[32] = {};
QString DbManager::sanitizeTableName(const QString& chId) {
QString s;
@@ -119,28 +118,6 @@ void DbManager::initTables() {
" updated_at INTEGER DEFAULT (unixepoch())"
");"
"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,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE TABLE IF NOT EXISTS node_addresses ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" node_id INTEGER NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,"
" family INTEGER NOT NULL CHECK(family IN (4, 6)),"
" protocol INTEGER NOT NULL DEFAULT 1,"
" address BLOB NOT NULL,"
" port INTEGER NOT NULL CHECK(port > 0 AND port <= 65535),"
" rtt INTEGER,"
" is_nat INTEGER DEFAULT 0,"
" created_at INTEGER DEFAULT (unixepoch())"
");"
"CREATE INDEX IF NOT EXISTS idx_na_node ON node_addresses(node_id);"
"CREATE TABLE IF NOT EXISTS accounts ("
" node_id INTEGER PRIMARY KEY REFERENCES nodes(node_id),"
" display_name TEXT NOT NULL,"
@@ -150,21 +127,6 @@ void DbManager::initTables() {
" 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())"
");"
"CREATE TABLE IF NOT EXISTS ui_state ("
" key TEXT PRIMARY KEY,"
" value TEXT"
@@ -213,12 +175,11 @@ 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: chId='%s' name='%s'", chId.toUtf8().constData(), name.toUtf8().constData());
QByteArray sig = kZero64; Q_UNUSED(sig);
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), pt = peersTableName(chId);
QString msgt = msgTableName(chId);
/* per-channel message table (DDL — до INSERT, чтобы не ломать транзакцию) */
QString csql = QStringLiteral(
"CREATE TABLE IF NOT EXISTS \"%1\" ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
@@ -237,41 +198,7 @@ bool DbManager::createChannel(const QString& chId, const QString& name,
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);
/* per-channel peers table */
csql = QStringLiteral(
"CREATE TABLE IF NOT EXISTS \"%1\" ("
" node_id INTEGER NOT NULL,"
" join_sig BLOB NOT NULL,"
" creator_sig BLOB,"
" comment TEXT,"
" joined_at INTEGER DEFAULT (unixepoch()),"
" PRIMARY KEY (node_id))").arg(pt);
sqlite3_exec(m_db, csql.toUtf8().constData(), nullptr, nullptr, nullptr);
/* вставка записи channels (после DDL, корректно внутри транзакции) */
sqlite3_stmt* stmt = prepareOrNull(
"INSERT INTO channels(channel_id,name,owner_node_id,is_dm,"
" x25519_pubkey,x25519_privkey,ed25519_pubkey,ed25519_privkey,signature)"
" VALUES(?,?,?,0,?,?,?,?,?)");
if (!stmt) return false;
sqlite3_bind_text(stmt, 1, chId.toUtf8().constData(), -1, SQLITE_TRANSIENT);
sqlite3_bind_text(stmt, 2, name.toUtf8().constData(), -1, SQLITE_TRANSIENT);
sqlite3_bind_int64(stmt, 3, (sqlite3_int64)ownerNodeId);
sqlite3_bind_blob(stmt, 4, x25519pub.isEmpty() ? kZero32 : x25519pub.constData(),
x25519pub.isEmpty() ? 32 : x25519pub.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 5, x25519priv.isEmpty() ? nullptr : x25519priv.constData(),
x25519priv.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 6, ed25519pub.isEmpty() ? kZero32 : ed25519pub.constData(),
ed25519pub.isEmpty() ? 32 : ed25519pub.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 7, ed25519priv.isEmpty() ? nullptr : ed25519priv.constData(),
ed25519priv.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 8, sig.constData(), sig.size(), SQLITE_TRANSIENT);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
if (!ok) { GUI_ERROR("createChannel %s failed: %s", chId.toUtf8().constData(), sqlite3_errmsg(m_db)); return false; }
GUI_INFO("Channel created: %s (msg=%s, peers=%s)",
chId.toUtf8().constData(), msgt.toUtf8().constData(), pt.toUtf8().constData());
GUI_INFO("Message table ensured: %s", msgt.toUtf8().constData());
return true;
}
@@ -284,6 +211,7 @@ bool DbManager::deleteChannel(const QString& chId) {
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;
}
@@ -349,73 +277,32 @@ bool DbManager::insertMessage(const QString& chId, quint64 nodeId,
return ok;
}
/* ── peers ── */
/* ── peers (запись теперь через topo_node_sqlite) ── */
bool DbManager::addPeer(const QString& chId, quint64 nodeId,
const QByteArray& joinSig, const QByteArray& creatorSig,
const QString& comment) {
QString sql = QStringLiteral(
"INSERT OR REPLACE INTO \"%1\"(node_id, join_sig, creator_sig, comment)"
" VALUES(?,?,?,?)").arg(peersTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_bind_blob(stmt, 2, joinSig.constData(), joinSig.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 3, creatorSig.isEmpty() ? nullptr : creatorSig.constData(),
creatorSig.size(), SQLITE_TRANSIENT);
if (!comment.isEmpty())
sqlite3_bind_text(stmt, 4, comment.toUtf8().constData(), -1, SQLITE_TRANSIENT);
else
sqlite3_bind_null(stmt, 4);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
}
bool DbManager::removePeer(const QString& chId, quint64 nodeId) {
QString sql = QStringLiteral(
"DELETE FROM \"%1\" WHERE node_id=?").arg(peersTableName(chId));
sqlite3_stmt* stmt = prepareOrNull(sql.toUtf8().constData());
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
(void)chId; (void)nodeId; (void)joinSig; (void)creatorSig; (void)comment;
return true;
}
/* ── nodes ── */
bool DbManager::removePeer(const QString& chId, quint64 nodeId) {
(void)chId; (void)nodeId;
return true;
}
/* ── nodes (запись теперь через topo_node_sqlite) ── */
bool DbManager::upsertNode(quint64 nodeId, const QString& name,
const QByteArray& x25519pub, const QByteArray& ed25519pub) {
sqlite3_stmt* stmt = prepareOrNull(
"INSERT OR REPLACE INTO nodes(node_id, name, x25519_pubkey, ed25519_pubkey, last_seen_at)"
" VALUES(?,?,?,?,unixepoch())");
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_bind_text(stmt, 2, name.toUtf8().constData(), -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 3, x25519pub.constData(), x25519pub.size(), SQLITE_TRANSIENT);
sqlite3_bind_blob(stmt, 4, ed25519pub.isEmpty() ? nullptr : ed25519pub.constData(),
ed25519pub.size(), SQLITE_TRANSIENT);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
(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) {
sqlite3_stmt* stmt = prepareOrNull(
"INSERT OR IGNORE INTO node_addresses(node_id, family, protocol, address, port, is_nat)"
" VALUES(?,?,?,?,?,?)");
if (!stmt) return false;
sqlite3_bind_int64(stmt, 1, (sqlite3_int64)nodeId);
sqlite3_bind_int(stmt, 2, family);
sqlite3_bind_int(stmt, 3, protocol);
sqlite3_bind_blob(stmt, 4, addr.constData(), addr.size(), SQLITE_TRANSIENT);
sqlite3_bind_int(stmt, 5, port);
sqlite3_bind_int(stmt, 6, isNat ? 1 : 0);
bool ok = sqlite3_step(stmt) == SQLITE_DONE;
sqlite3_finalize(stmt);
return ok;
(void)nodeId; (void)family; (void)protocol; (void)addr; (void)port; (void)isNat;
return true;
}
QByteArray DbManager::loadNodeInfo(quint64 nodeId) const {
@@ -755,7 +642,7 @@ void DbManager::setNodeOnline(quint64 nodeId, bool online) {
/* ── seed stub data ── */
void DbManager::seedStubData() {
GUI_INFO("Seeding stub data...");
GUI_INFO("Seeding stub data (accounts only)...");
sqlite3_exec(m_db, "BEGIN", nullptr, nullptr, nullptr);
unsigned char pub_dummy[32] = {};
@@ -773,14 +660,6 @@ void DbManager::seedStubData() {
{5, "Eve", "E", "#00B894"},
};
for (auto& a : accs) {
ins = prepareOrNull("INSERT OR IGNORE INTO nodes(node_id,name,x25519_pubkey,ed25519_pubkey,last_seen_at)"
" VALUES(?,?,?,?,unixepoch())");
sqlite3_bind_int64(ins, 1, (sqlite3_int64)a.id);
sqlite3_bind_text(ins, 2, a.name, -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(ins, 3, pub_dummy, 32, SQLITE_TRANSIENT);
sqlite3_bind_blob(ins, 4, pub_dummy, 32, SQLITE_TRANSIENT);
sqlite3_step(ins); sqlite3_finalize(ins);
ins = prepareOrNull("INSERT OR IGNORE INTO accounts(node_id,display_name,avatar_color,avatar_letter)"
" VALUES(?,?,?,?)");
sqlite3_bind_int64(ins, 1, (sqlite3_int64)a.id);
@@ -790,17 +669,6 @@ void DbManager::seedStubData() {
sqlite3_step(ins); sqlite3_finalize(ins);
}
/* channels (1 демо-канал) */
QByteArray dummyPub(32, '\0'), dummySig(64, '\0'), dummyCH(32, '\0');
createChannel("ch:general", "#general", 0, dummyPub, QByteArray(), dummyPub, QByteArray());
/* peers */
for (auto& a : accs) {
addPeer("ch:general", a.id, dummySig, dummySig, a.name);
}
/* messages — 0 для простоты, добавим позже в seed v2 */
sqlite3_exec(m_db, "COMMIT", nullptr, nullptr, nullptr);
GUI_INFO("Seed data inserted (5 accounts, 1 channel)");
GUI_INFO("Seed data inserted (5 accounts)");
}