From 9d48821af25cbb2a3e9f5a9444bcf80560957751 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 23:39:34 +0100 Subject: [PATCH] Own message list requests and acknowledge PM text persistence --- src/dm/dm_core.c | 2 + src/dm/dm_core.h | 1 + tools/chatgui/src/messagelist.cpp | 102 +++++++++++++----------------- 3 files changed, 48 insertions(+), 57 deletions(-) diff --git a/src/dm/dm_core.c b/src/dm/dm_core.c index 81a52e4b..fcf294e7 100644 --- a/src/dm/dm_core.c +++ b/src/dm/dm_core.c @@ -1126,7 +1126,9 @@ void dm_send_trampoline(void* arg) { struct UTUN_INSTANCE* inst = req->inst; char conv[64]; strncpy(conv, req->conv_id, sizeof(conv) - 1); conv[sizeof(conv) - 1] = '\0'; uint64_t generation = req->generation ? req->generation : dm_history_generation(inst, conv); + uint64_t request_id = req->request_id; int rc = dm_history_current(inst, conv, generation) ? dm_send(inst, conv, "text", req->data, req->data_len) : -1; + chat_event_submitted(inst, request_id, rc == 0); u_free(arg); if (rc != 0) DEBUG_ERROR(DEBUG_CATEGORY_DM, "%s: dm_send_trampoline — dm_send failed conv=%s", diff --git a/src/dm/dm_core.h b/src/dm/dm_core.h index 68c5bb9f..ec5ea332 100644 --- a/src/dm/dm_core.h +++ b/src/dm/dm_core.h @@ -157,6 +157,7 @@ struct dm_send_req { struct UTUN_INSTANCE* inst; /* Заимствованное ядро. */ char conv_id[64]; /* Десятичный ID беседы с NUL. */ uint64_t generation; /* 0: захватить при входе в uasync, до любой фоновой работы. */ + uint64_t request_id; /* Ненулевой ID composer: результат локальной записи через CHAT_EVT_MSG_SUBMITTED. */ uint32_t data_len; /* Число байт текста, до DM_DATA_MAX. */ uint8_t* data; /* Указывает на data_len байт в том же блоке после структуры. */ }; diff --git a/tools/chatgui/src/messagelist.cpp b/tools/chatgui/src/messagelist.cpp index 18ae6c6b..93355258 100644 --- a/tools/chatgui/src/messagelist.cpp +++ b/tools/chatgui/src/messagelist.cpp @@ -59,7 +59,6 @@ extern "C" { #include "../../lib/mem.h" #include "../../lib/audio_compressor.h" #include "../../lib/debug_config.h" -void chat_core_save_ui_state_trampoline(void* arg); } #include "radio_audio_engine.h" @@ -596,9 +595,9 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent } auto* arg = (chat_sound_arg*)u_calloc(1, sizeof(chat_sound_arg)); if (!arg) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group menu: sound allocation failed"); return; } - arg->inst = gui_bridge_get_inst(); arg->on = sound->isChecked(); + arg->on = sound->isChecked(); snprintf(arg->ch_id, sizeof(arg->ch_id), "%s", channel.toUtf8().constData()); - gui_bridge_post_uasync_fn(chat_core_set_sound_on_message_trampoline, arg); + if (gui_bridge_post_bound_fn(chat_core_set_sound_on_message_trampoline, arg, &arg->inst) < 0) u_free(arg); } }); @@ -639,10 +638,10 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent if (target.isEmpty() || target.startsWith("dm:") || id <= 0) return false; auto* req = (struct attachment_dl_req*)u_calloc(1, sizeof(struct attachment_dl_req)); if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment: download request allocation failed"); return false; } - req->inst = gui_bridge_get_inst(); req->msg_id = id; + req->msg_id = id; snprintf(req->channel_id, sizeof(req->channel_id), "%s", target.toUtf8().constData()); - gui_bridge_post_uasync_fn(chat_core_attachment_download_trampoline, req); - return true; + if (gui_bridge_post_bound_fn(chat_core_attachment_download_trampoline, req, &req->inst) == 0) return true; + u_free(req); return false; }); m_view->setItemDelegate(msgDelegate); m_view->setEditTriggers(QAbstractItemView::NoEditTriggers); @@ -692,7 +691,7 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent } auto* req = (struct chat_msg_submit*)u_calloc(1, sizeof(struct chat_msg_submit) + bytes.size()); if (!req) { GUI_ERROR("edit: allocation failed"); m_inputBar->submissionResult(request, false); return; } - req->inst = gui_bridge_get_inst(); req->request_id = request; req->expected_ver = version; + req->request_id = request; req->expected_ver = version; snprintf(req->channel_id, sizeof(req->channel_id), "%s", channel.toUtf8().constData()); memcpy(req->message_id, id.constData(), 32); req->data = (uint8_t*)(req + 1); req->data_len = bytes.size(); memcpy(req->data, bytes.constData(), bytes.size()); @@ -710,20 +709,23 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent if (isDmChannel(m_currentChannelId)) { if (!m_currentDmGeneration || m_dmDeleting.value(dmConvId(m_currentChannelId)) == m_currentDmGeneration) { - GUI_WARN("PM text rejected: unavailable/deleting target=%s", qPrintable(m_currentChannelId)); return; + GUI_WARN("PM text rejected: unavailable/deleting target=%s", qPrintable(m_currentChannelId)); + m_inputBar->submissionResult(request, false); return; } QByteArray textData = text.toUtf8(); QString conv = dmConvId(m_currentChannelId); struct dm_send_req* req = (struct dm_send_req*)u_malloc(sizeof(struct dm_send_req) + textData.size()); - if (!req) return; + if (!req) { GUI_ERROR("PM text request allocation failed"); m_inputBar->submissionResult(request, false); return; } memset(req, 0, sizeof(*req)); - req->inst = gui_bridge_get_inst(); req->generation = m_currentDmGeneration; + req->request_id = request; strncpy(req->conv_id, conv.toUtf8().constData(), sizeof(req->conv_id) - 1); req->data = (uint8_t*)(req + 1); req->data_len = (uint32_t)textData.size(); memcpy(req->data, textData.constData(), textData.size()); - gui_bridge_post_uasync_fn(dm_send_trampoline, req); + if (gui_bridge_post_bound_fn(dm_send_trampoline, req, &req->inst) < 0) { + u_free(req); m_inputBar->submissionResult(request, false); + } return; } @@ -748,17 +750,17 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent connect(m_inputBar, &InputBar::submitAttachment, this, [this](const QString& target, struct attachment_send_req* req) { const bool dm = isDmChannel(target); - if (!gui_bridge_get_inst() || !gui_bridge_is_uasync_ready() || - (dm && (!req->dm_generation || m_dmDeleting.value(dmConvId(target)) == req->dm_generation))) { + if (dm && (!req->dm_generation || m_dmDeleting.value(dmConvId(target)) == req->dm_generation)) { GUI_ERROR("send: attachment target/core unavailable target=%s generation=%llu", qPrintable(target), (unsigned long long)req->dm_generation); m_inputBar->submissionResult(req->request_id, false); - if (req->pcm_release) req->pcm_release(req->pcm_owner); else u_free((void*)req->pcm); - audio_compressor_destroy(req->compressor); u_free(req); return; + attachment_send_discard(req); return; } - req->inst = gui_bridge_get_inst(); req->is_dm = isDmChannel(target); + req->is_dm = dm; QByteArray id = (req->is_dm ? dmConvId(target) : target).toUtf8(); snprintf(req->target, sizeof(req->target), "%s", id.constData()); - gui_bridge_post_uasync_fn(attachment_send_trampoline, req); + if (gui_bridge_submit_attachment(req) < 0) { + m_inputBar->submissionResult(req->request_id, false); attachment_send_discard(req); + } }); connect(m_inputBar, &InputBar::sendFileAttachment, this, [this](const QString& target, const QString& mediaSrc, const QString& mediaDest, @@ -769,21 +771,24 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent if (isDmChannel(target)) { if (target != m_currentChannelId || !m_currentDmGeneration || m_dmDeleting.value(dmConvId(target)) == m_currentDmGeneration) { - GUI_WARN("PM file rejected: unavailable/deleting target=%s", qPrintable(target)); return; + GUI_WARN("PM file rejected: unavailable/deleting target=%s", qPrintable(target)); + m_inputBar->submissionResult(request, false); return; } auto* req = (struct dm_file_req*)u_calloc(1, sizeof(struct dm_file_req)); - if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM file request allocation failed"); return; } - req->inst = gui_bridge_get_inst(); + if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM file request allocation failed"); m_inputBar->submissionResult(request, false); return; } req->generation = m_currentDmGeneration; QByteArray conv = dmConvId(target).toUtf8(), path = mediaSrc.toUtf8(), name = displayName.toUtf8(); if (path.size() >= (int)sizeof(req->path) || name.size() >= (int)sizeof(req->info.name)) { - DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM attachment path or name is too long"); u_free(req); return; + DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM attachment path or name is too long"); + u_free(req); m_inputBar->submissionResult(request, false); return; } snprintf(req->conv, sizeof(req->conv), "%s", conv.constData()); snprintf(req->path, sizeof(req->path), "%s", path.constData()); req->info.kind = ATTACHMENT_FILE; snprintf(req->info.name, sizeof(req->info.name), "%s", name.constData()); - gui_bridge_post_uasync_fn(dm_send_file_trampoline, req); + if (gui_bridge_post_bound_fn(dm_send_file_trampoline, req, &req->inst) < 0) { + u_free(req); m_inputBar->submissionResult(request, false); + } return; } @@ -1104,15 +1109,7 @@ void MessageList::saveChannelState() { auto postSaveKey = [&cid](const char* suffix, qint64 val) { QByteArray key = "ch_" + cid + suffix; QByteArray valStr = QByteArray::number(val); - size_t keyLen = (size_t)key.size(), valLen = (size_t)valStr.size(); - size_t total = keyLen + 1 + valLen + 1; - if (total > 256) return; - struct save_ui_state_arg* arg = (struct save_ui_state_arg*)u_malloc(sizeof(struct save_ui_state_arg)); - if (!arg) return; - arg->inst = gui_bridge_get_inst(); - memcpy(arg->data, key.constData(), keyLen + 1); - memcpy(arg->data + keyLen + 1, valStr.constData(), valLen + 1); - gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg); + return gui_bridge_post_setting(GUI_SETTING_UI_STATE, key.constData(), valStr.constData()) == 0; }; if (lastReadMsgId > 0) @@ -1200,21 +1197,14 @@ void MessageList::updateReadPosition() { auto postSaveKey = [&cid](const char* suffix, qint64 val) { QByteArray key = "ch_" + cid + suffix; QByteArray valStr = QByteArray::number(val); - size_t keyLen = (size_t)key.size(), valLen = (size_t)valStr.size(); - size_t total = keyLen + 1 + valLen + 1; - if (total > 256) return; - struct save_ui_state_arg* arg = (struct save_ui_state_arg*)u_malloc(sizeof(struct save_ui_state_arg)); - if (!arg) return; - arg->inst = gui_bridge_get_inst(); - memcpy(arg->data, key.constData(), keyLen + 1); - memcpy(arg->data + keyLen + 1, valStr.constData(), valLen + 1); - gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg); + return gui_bridge_post_setting(GUI_SETTING_UI_STATE, key.constData(), valStr.constData()) == 0; }; if (newReadMsgId > m_lastReadMsgId) { - m_lastReadMsgId = newReadMsgId; - postSaveKey("_last_read", m_lastReadMsgId); - emit readPositionChanged(m_currentChannelId, m_lastReadMsgId); + if (postSaveKey("_last_read", newReadMsgId)) { + m_lastReadMsgId = newReadMsgId; + emit readPositionChanged(m_currentChannelId, m_lastReadMsgId); + } } postSaveKey("_last_pos", lastPosMsgId); @@ -1460,12 +1450,11 @@ void MessageList::onMessageContextMenu(const QPoint& pos) { if (chId.isEmpty() || msgId <= 0) return; struct attachment_dl_req* req = (struct attachment_dl_req*) u_malloc(sizeof(struct attachment_dl_req)); - if (!req) return; + if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "download request allocation failed"); return; } memset(req, 0, sizeof(*req)); - req->inst = gui_bridge_get_inst(); strncpy(req->channel_id, chId.toUtf8().constData(), sizeof(req->channel_id) - 1); req->msg_id = msgId; - gui_bridge_post_uasync_fn(chat_core_attachment_download_trampoline, req); + if (gui_bridge_post_bound_fn(chat_core_attachment_download_trampoline, req, &req->inst) < 0) u_free(req); } } @@ -1481,14 +1470,11 @@ void MessageList::clearDmCallRoute() { void MessageList::refreshDmCallRoute() { if (!isDmChannel(m_currentChannelId)) return; - auto* inst = gui_bridge_get_inst(); - if (!inst) { clearDmCallRoute(); return; } auto* req = (struct dm_messages_req*)u_calloc(1, sizeof(struct dm_messages_req)); if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "PM route request: allocation failed"); clearDmCallRoute(); return; } - req->inst = inst; QByteArray conv = dmConvId(m_currentChannelId).toUtf8(); snprintf(req->conv_id, sizeof(req->conv_id), "%s", conv.constData()); - gui_bridge_post_uasync_fn(dm_call_route_trampoline, req); + if (gui_bridge_post_bound_fn(dm_call_route_trampoline, req, &req->inst) < 0) { u_free(req); clearDmCallRoute(); } } void MessageList::onDmCallRoute(const uint8_t* data, int len) { @@ -1543,12 +1529,11 @@ void MessageList::requestDmMessages(const QString& channelId) { } QString conv = dmConvId(channelId); struct dm_messages_req* req = (struct dm_messages_req*)u_malloc(sizeof(*req)); - if (!req) return; + if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM history request allocation failed"); return; } memset(req, 0, sizeof(*req)); - req->inst = gui_bridge_get_inst(); req->generation = m_currentDmGeneration; strncpy(req->conv_id, conv.toUtf8().constData(), sizeof(req->conv_id) - 1); - gui_bridge_post_uasync_fn(dm_messages_trampoline, req); + if (gui_bridge_post_bound_fn(dm_messages_trampoline, req, &req->inst) < 0) u_free(req); } void MessageList::onAttachmentPreparation(const uint8_t* data, int len) { @@ -1742,18 +1727,21 @@ void MessageList::onRadioToggled(bool on) { m_radioBtn->setChecked(m_radioOn); return; } - a->inst = gui_bridge_get_inst(); a->group_id = groupId; a->on = on ? 1 : 0; - gui_bridge_post_uasync_fn(radio_set_active_trampoline, a); + if (gui_bridge_post_bound_fn(radio_set_active_trampoline, a, &a->inst) < 0) { + u_free(a); + if (!on) RadioAudioEngine::instance()->stop(); + QSignalBlocker blocker(m_radioBtn); m_radioBtn->setChecked(m_radioOn); return; + } if (on) { if (RadioAudioEngine::instance()->start(groupId) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: radio start failed group=%016llx", (unsigned long long)groupId); auto* undo = static_cast(u_calloc(1, sizeof(radio_set_active_arg))); if (undo) { - undo->inst = gui_bridge_get_inst(); undo->group_id = groupId; - gui_bridge_post_uasync_fn(radio_set_active_trampoline, undo); + undo->group_id = groupId; + if (gui_bridge_post_bound_fn(radio_set_active_trampoline, undo, &undo->inst) < 0) u_free(undo); } else DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: cannot cancel failed radio subscription"); QSignalBlocker blocker(m_radioBtn); m_radioBtn->setChecked(false);