Preserve failed composer drafts and cancel PM submissions with a result

This commit is contained in:
evgeny committed 2026-10-28 00:59:00 +01:00
1 parent 9d48821af2
commit f87ee63a1d
3 files changed
+41 -10

No files matched your search

+8 -2
View File
@@ -279,7 +279,7 @@ bool InputBar::submissionPending() const {
/* Канальный текст и каждая часть ответа имеют собственное подтверждение и снимок черновика. */
quint64 InputBar::beginSubmission(const QByteArray& reply, quint64 generation, const QString& target,
const QString& text, const QString& source) {
if (reply.isEmpty() && (m_dmMode || text.isEmpty())) return 0;
if (reply.isEmpty() && text.isEmpty()) return 0;
const quint64 request = ++s_submissionSequence;
m_pendingSubmissions.insert(request, {generation, target, text, source, m_input->document()->revision(), m_attachmentRevision});
GUI_DEBUG("send: pending ch=%s reply=%s request=%llu", qPrintable(target), reply.toHex().constData(), (unsigned long long)request);
@@ -287,6 +287,11 @@ quint64 InputBar::beginSubmission(const QByteArray& reply, quint64 generation, c
}
void InputBar::submissionResult(quint64 request, bool success) {
if (!request && !success) {
m_submissionFailed = true;
GUI_ERROR("send: untracked attachment rejected target=%s", qPrintable(m_channelIdForRecord));
emit attachmentRejected(QCoreApplication::translate("ChatGui", "Could not send message. Please try again.")); return;
}
if (request && request == m_editRequest) {
m_editRequest = 0; m_input->setReadOnly(false);
if (success) cancelEdit();
@@ -515,13 +520,14 @@ void InputBar::onSendClicked() {
emit sendFileAttachment(target, attachmentSource, attachmentDestination, attachmentName, reply, requestId);
}
if (!alive || context != m_attachmentContext) return;
if (m_submissionFailed) return;
if (reply.isEmpty()) clearPendingAttachment();
}
if (!text.isEmpty()) {
const quint64 request = beginSubmission(reply, m_replyGeneration, m_channelIdForRecord, text);
emit sendMessage(text, reply, request);
if (!alive || context != m_attachmentContext) return;
if (!request) m_input->clear();
if (!request && !m_submissionFailed) m_input->clear();
if (m_emojiPanel) m_emojiPanel->hide();
m_input->setFocus();
}
+3
View File
@@ -135,6 +135,9 @@ int gui_bridge_update_name(const char* name);
* При -1 результата нет: вызывающий освобождает req и сам сообщает об отказе своему composer. */
struct chat_msg_submit;
int gui_bridge_submit_message(struct chat_msg_submit* req);
/* PM-текст с request_id: привязка/владение/отмена и MSG_SUBMITTED как у канального composer. */
struct dm_send_req;
int gui_bridge_submit_dm_message(struct dm_send_req* req);
/* Подготовка вложения: тот же контракт результата, но отмена также освобождает PCM/compressor/временный source
* и сообщает MSG_SUBMITTED(false). При -1 caller вызывает attachment_send_discard(req). */
struct attachment_send_req;
+30 -8
View File
@@ -1019,14 +1019,19 @@ int gui_bridge_update_name(const char* name) {
return 0;
}
/* Общий codec composer: ID little-endian и success=0, без вызова GUI под mutex. */
static void post_submission_failure(uint64_t id) {
if (!id) return;
uint8_t result[9] = {0};
for (unsigned i = 0; i < 8; ++i) result[i] = (uint8_t)(id >> (8 * i));
gui_bridge_post(CHAT_EVT_MSG_SUBMITTED, result, sizeof(result));
}
/* Отмена выполняется в uasync, но не вызывает обработчик ядра под bridge_mutex; результат копируется в Qt-очередь. */
static void cancel_message_submit(void* arg) {
auto* request = static_cast<chat_msg_submit*>(arg);
DEBUG_WARN(DEBUG_CATEGORY_CHAT_SYNC, "composer submission cancelled: request=%llu", (unsigned long long)request->request_id);
if (!request->request_id) return;
uint8_t result[9] = {0};
for (unsigned i = 0; i < 8; ++i) result[i] = (uint8_t)(request->request_id >> (8 * i));
gui_bridge_post(CHAT_EVT_MSG_SUBMITTED, result, sizeof(result));
post_submission_failure(request->request_id);
}
int gui_bridge_submit_message(struct chat_msg_submit* req) {
@@ -1038,13 +1043,30 @@ int gui_bridge_submit_message(struct chat_msg_submit* req) {
return post_command(chat_core_submit_trampoline, req, true, cancel_message_submit);
}
/* PM composer получает отказ также при detach до исполнения native trampoline. */
static void cancel_dm_submit(void* arg) {
auto* request = static_cast<dm_send_req*>(arg);
DEBUG_WARN(DEBUG_CATEGORY_DM, "PM submission cancelled: request=%llu", (unsigned long long)request->request_id);
post_submission_failure(request->request_id);
}
int gui_bridge_submit_dm_message(struct dm_send_req* req) {
std::lock_guard<std::mutex> guard(bridge_mutex);
if (!req || !g_inst || !g_ua || !uasync_is_running(g_ua)) {
DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM submission: core/request unavailable"); return -1;
}
req->inst = g_inst;
if (post_command(dm_send_trampoline, req, true, cancel_dm_submit) < 0) {
req->inst = nullptr; DEBUG_ERROR(DEBUG_CATEGORY_DM, "PM submission: queue allocation failed"); return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_DM, "PM text queued: conversation=%s request=%llu", req->conv_id, (unsigned long long)req->request_id);
return 0;
}
/* Отмена до native trampoline не требует живого media owner; callback освобождает весь запрос сам. */
static void cancel_attachment_submit(void* arg) {
auto* request = static_cast<attachment_send_req*>(arg);
if (request->request_id) {
uint8_t result[9] = {0};
memcpy(result, &request->request_id, 8); gui_bridge_post(CHAT_EVT_MSG_SUBMITTED, result, sizeof(result));
}
post_submission_failure(request->request_id);
attachment_send_discard(request);
}