fix(chat): node online status race, WELCOME peer_needed off-by-one, JOIN missing proto byte

- node_put/node_update_verified: set online=1 on INSERT (nodes created when conn is UP)
- channel_peers_all: fix peer_needed calculation (atl byte was missing)
- cs_send_channel_join: add proto byte to address serialization matching all parsers
This commit is contained in:
evgeny committed 2026-08-06 01:18:27 +03:00
1 parent 38eb51c679
commit f476188566
18 files changed
+431 -24

No files matched your search

+148
View File
@@ -1,14 +1,17 @@
#include "mainwindow.h"
#include "channellist.h"
#include "messagelist.h"
#include "messagedelegate.h"
#include "accountlist.h"
#include "invitedialog.h"
#include "inviteby.h"
#include "joindialog.h"
#include "settingsdialog.h"
#include "channelsettingsdialog.h"
#include "creategroupdialog.h"
#include "invite_link.h"
#include "sound_manager.h"
#include "voiceplayback.h"
#include "audiorecorder.h"
#include "voicemessageencoder.h"
@@ -31,11 +34,15 @@ extern "C" {
#include <QTextStream>
#include <QDir>
#include <cmath>
#include <QJsonDocument>
#include <QJsonObject>
extern "C" {
#include "chat/chat_core.h"
#include "../../lib/mem.h"
#include "../../lib/debug_config.h"
void chat_core_attachment_download_trampoline(void* arg);
void chat_core_save_ui_state_trampoline(void* arg);
}
static MainWindow* s_mainWindow = nullptr;
@@ -79,6 +86,7 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
, m_cfgDebugFile(debugFile), m_cfgDebugLevel(debugLevel)
, m_cfgDebugCategories(debugCategories)
, m_recorder(nullptr)
, m_autoPlayTimer(new QTimer(this))
{
setWindowTitle("Chat");
resize(900, 600);
@@ -92,6 +100,7 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
setupNode();
setupConnects();
}
connect(m_autoPlayTimer, &QTimer::timeout, this, &MainWindow::autoPlayTimerTick);
}
MainWindow::~MainWindow() {
@@ -263,6 +272,7 @@ void MainWindow::setupBridgeCallbacks() {
QString chId = QString::fromUtf8(ch_id, ch_id_len);
s_mainWindow->m_channelList->loadChannels();
s_mainWindow->m_messageList->onAttachmentDownloaded(chId, msg_id);
s_mainWindow->onAttachmentDownloadedForAutoPlay(chId, msg_id);
});
gui_bridge_set_download_progress_cb([](const char* ch_id, int ch_id_len, int64_t msg_id, int blocks_done, int num_blocks) {
if (!s_mainWindow) return;
@@ -287,6 +297,7 @@ void MainWindow::setupConnects() {
connect(m_channelList, &ChannelList::joinRequested, this, &MainWindow::showJoinDialog);
connect(m_channelList, &ChannelList::createGroupRequested, this, &MainWindow::onCreateGroupRequested);
connect(m_channelList, &ChannelList::settingsRequested, this, &MainWindow::showSettings);
connect(m_channelList, &ChannelList::channelSettingsRequested, this, &MainWindow::showChannelSettings);
connect(m_accountList, &AccountList::memberRenamed,
m_messageList, &MessageList::reloadLocalNicks);
connect(m_messageList, &MessageList::readPositionChanged,
@@ -422,6 +433,10 @@ void MainWindow::onMessageReceived(const char* ch_id, int ch_id_len, uint64_t au
}
if (m_db && m_db->getUiStateInt("channel_sort", 0) == 1)
reloadChannels();
if (m_db && m_db->isOpen() && m_db->getUiStateInt(QStringLiteral("ch_%1_auto_play_voice").arg(cid), 0)) {
QTimer::singleShot(100, this, [this, cid]() { autoPlayCheckNewMessage(cid); });
}
}
void MainWindow::onMembersChanged(const char* ch_id) {
@@ -479,3 +494,136 @@ void MainWindow::onCreateGroupRequested() {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "MainWindow: create group request posted name=%s", qPrintable(name));
}
void MainWindow::showChannelSettings(const QString& channelId) {
ChannelSettingsDialog dlg(channelId, m_db, this);
dlg.exec();
}
QString MainWindow::autoPlayMediaDir() const {
if (m_db && m_db->isOpen())
return m_db->dbPath() + QStringLiteral("/media");
return QString();
}
void MainWindow::autoPlayCheckNewMessage(const QString& channelId) {
if (!m_db || !m_db->isOpen()) return;
auto msgs = m_db->getMessages(channelId, 1);
if (msgs.isEmpty()) return;
const MessageRow& msg = msgs.first();
if (msg.isOutgoing || msg.contentType != QStringLiteral("audio/opus"))
return;
AutoPlayItem item;
item.channelId = channelId;
item.msgId = msg.id;
item.downloadStarted = false;
item.durationSec = 0.0f;
QByteArray data = msg.data;
if (data.startsWith("wf=")) {
QString raw = QString::fromUtf8(data);
int durPos = raw.indexOf(";dur=", 3);
if (durPos > 0) {
int durEnd = raw.indexOf(';', durPos + 5);
if (durEnd > 0) {
int durMs = raw.mid(durPos + 5, durEnd - durPos - 5).toInt();
item.durationSec = durMs / 1000.0f;
}
}
}
QJsonObject la = QJsonDocument::fromJson(msg.localAttrs).object();
QString fp = la.value(QStringLiteral("fp")).toString();
if (!fp.isEmpty()) {
QString fullPath = autoPlayMediaDir() + QStringLiteral("/") + channelId + QStringLiteral("/") + fp;
if (QFile::exists(fullPath)) {
m_autoPlayQueue.enqueue(item);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "auto-play: enqueue ch=%s msg=%lld file=%s",
qPrintable(channelId), (long long)msg.id, qPrintable(fullPath));
if (!m_autoPlayTimer->isActive())
m_autoPlayTimer->start(200);
if (!SoundManager::instance()->isCurrentPcmPlaying())
autoPlayTimerTick();
return;
}
}
struct attachment_dl_req* req = (struct attachment_dl_req*)
u_malloc(sizeof(struct attachment_dl_req));
if (!req) return;
memset(req, 0, sizeof(*req));
strncpy(req->channel_id, channelId.toUtf8().constData(), sizeof(req->channel_id) - 1);
req->msg_id = msg.id;
gui_bridge_post_uasync_fn(chat_core_attachment_download_trampoline, req);
item.downloadStarted = true;
m_autoPlayQueue.enqueue(item);
m_autoPlayDownloading.insert(msg.id);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "auto-play: download started ch=%s msg=%lld",
qPrintable(channelId), (long long)msg.id);
if (!m_autoPlayTimer->isActive())
m_autoPlayTimer->start(200);
}
void MainWindow::autoPlayTimerTick() {
if (SoundManager::instance()->isCurrentPcmPlaying())
return;
while (!m_autoPlayQueue.isEmpty()) {
AutoPlayItem& item = m_autoPlayQueue.head();
MessageRow msg = m_db->getMessageById(item.channelId, item.msgId);
if (msg.id == 0) {
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "auto-play: msg %lld not found in DB, removing from queue",
(long long)item.msgId);
m_autoPlayQueue.dequeue();
continue;
}
QJsonObject la = QJsonDocument::fromJson(msg.localAttrs).object();
QString fp = la.value(QStringLiteral("fp")).toString();
if (!fp.isEmpty()) {
QString fullPath = autoPlayMediaDir() + QStringLiteral("/") + item.channelId
+ QStringLiteral("/") + fp;
if (QFile::exists(fullPath)) {
m_autoPlayQueue.dequeue();
m_autoPlayDownloading.remove(item.msgId);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "auto-play: playing ch=%s msg=%lld file=%s dur=%.1fs",
qPrintable(item.channelId), (long long)item.msgId, qPrintable(fullPath),
(double)item.durationSec);
MessageDelegate::stopAllAutoPlay();
VoicePlayer::playOpusFile(fullPath);
MessageDelegate::startAutoPlay(fullPath, item.durationSec);
return;
}
}
if (!item.downloadStarted && !m_autoPlayDownloading.contains(item.msgId)) {
struct attachment_dl_req* req = (struct attachment_dl_req*)
u_malloc(sizeof(struct attachment_dl_req));
if (!req) return;
memset(req, 0, sizeof(*req));
strncpy(req->channel_id, item.channelId.toUtf8().constData(), sizeof(req->channel_id) - 1);
req->msg_id = item.msgId;
gui_bridge_post_uasync_fn(chat_core_attachment_download_trampoline, req);
item.downloadStarted = true;
m_autoPlayDownloading.insert(item.msgId);
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "auto-play: download started ch=%s msg=%lld (from queue)",
qPrintable(item.channelId), (long long)item.msgId);
}
return;
}
m_autoPlayTimer->stop();
}
void MainWindow::onAttachmentDownloadedForAutoPlay(const QString& chId, int64_t msgId) {
(void)chId;
if (m_autoPlayDownloading.remove((qint64)msgId) == 0 && m_autoPlayQueue.isEmpty())
return;
if (!m_autoPlayTimer->isActive())
m_autoPlayTimer->start(200);
if (!SoundManager::instance()->isCurrentPcmPlaying())
autoPlayTimerTick();
}