diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index 75eb5bec..eaeb1dfd 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -76,7 +76,7 @@ void gui_bridge_set_uasync(struct UASYNC* ua); /* Опубликовать готовый instance из uasync после set_uasync. NULL снимается в uasync до destroy, * вне callbacks: синхронно отменяет ожидающие команды и освобождает аргументы owned-запросов. * Начатый callback завершается до остановки poll; отмена post_uasync_* не вызывает fn. - * Специализированные API (member_edit.h) могут доставить результат отмены в GUI. */ + * Специализированные API (member_edit.h, member_files_*) доставляют результат отмены в GUI. */ void gui_bridge_set_inst(struct UTUN_INSTANCE* inst); /* Текущий заимствованный UTUN_INSTANCE (NULL пока не создан/после сброса моста). */ @@ -283,6 +283,18 @@ void gui_bridge_set_dm_delete_result_cb(gui_dm_messages_fn cb); /* Файловый браузер: wire-ответы и прогресс общих передач, в GUI-потоке. */ void gui_bridge_set_member_files_response_cb(gui_dm_messages_fn cb); void gui_bridge_set_member_files_transfer_cb(gui_dm_messages_fn cb); +struct mf_message; +struct mf_transfer_request; +/* Скопировать команду/передачу и привязать instance под mutex моста. Исходные данные после возврата не нужны. + * Ненулевые group/server и request_id/id обязательны; transfer требует owner, поле inst входа игнорируется. + * 0 — принято: обычный RESPONSE/TRANSFER, в том числе MF_CANCELLED при отмене до trampoline. + * -1 — не принято: результата не будет, память не остаётся, вызывающий немедленно завершает операцию с ошибкой. + * Нужны gui_bridge_init и опубликованное работающее ядро; UI callbacks всегда через очередь Qt. */ +int gui_bridge_member_files_request(uint64_t group, uint64_t server, const struct mf_message* message); +int gui_bridge_member_files_transfer(const struct mf_transfer_request* request); +/* Запросить отмену ненулевого ID у текущего ядра; 0 — принято, -1 — отказ с логом. + * Память команды принадлежит мосту. Итог операции приходит по её RESPONSE/TRANSFER, отдельного ответа cancel нет. */ +int gui_bridge_member_files_cancel(uint64_t id); void gui_bridge_set_avatar_result_cb(gui_dm_messages_fn cb); void gui_bridge_set_group_avatar_result_cb(gui_dm_messages_fn cb); /* Опросы/реакции: адресное изменение и коррелированный ответ команды, в GUI-потоке. */ diff --git a/tools/chatgui/transport/gui_bridge_impl.cpp b/tools/chatgui/transport/gui_bridge_impl.cpp index 2fea9eac..d9f47d28 100644 --- a/tools/chatgui/transport/gui_bridge_impl.cpp +++ b/tools/chatgui/transport/gui_bridge_impl.cpp @@ -19,7 +19,7 @@ extern "C" { #include "../../../lib/debug_config.h" #include "../../../src/routing_layer/topo_node.h" #include "../../../src/routing_layer/topo_group.h" -#include "../../../src/member_files/protocol.h" +#include "../../../src/member_files/member_files.h" #include "../../../src/chat/chat_member.h" #include "../../../src/chat/chat_core.h" #include "../../../src/chat/chat_sync.h" @@ -667,6 +667,32 @@ void GuiBridgeReceiver::processPost(int eventType, QByteArray data) { } } +/* Отмена post сообщает результат через Qt, не вызывая код клиента/старого instance под bridge_mutex. */ +static void cancel_member_files_request(void* arg) { + auto* request = static_cast(arg); + auto& message = request->message; + message.kind = MF_RESPONSE; message.status = MF_CANCELLED; message.data_len = 0; message.more = 0; + uint8_t event[16 + MF_WIRE_MAX]; + mf_put64(event, request->group); mf_put64(event + 8, request->server); + int length = mf_encode(&message, event + 16, MF_WIRE_MAX); + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file bridge: request cancelled id=%llu command=%s group=%llu server=%llu path=%s", + (unsigned long long)message.request_id, mf_command_text(message.command), (unsigned long long)request->group, + (unsigned long long)request->server, message.path); + if (length > 0) gui_bridge_post(CHAT_EVT_MEMBER_FILES_RESPONSE, event, length + 16); +} + +/* До trampoline дисковый I/O ещё не начат: достаточно одного финального события, payload освободит мост. */ +static void cancel_member_files_transfer(void* arg) { + auto* request = static_cast(arg); + uint8_t event[51]{}; + mf_put64(event, request->id); mf_put64(event + 8, request->group); mf_put64(event + 16, request->server); + mf_put64(event + 24, request->owner); event[48] = MF_CANCELLED; event[50] = 2; + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file bridge: transfer cancelled id=%llu group=%llu server=%llu owner=%llu upload=%d remote=%s", + (unsigned long long)request->id, (unsigned long long)request->group, (unsigned long long)request->server, + (unsigned long long)request->owner, request->upload, request->remote); + gui_bridge_post(CHAT_EVT_MEMBER_FILES_TRANSFER, event, sizeof(event)); +} + /* ── C API ── */ extern "C" { @@ -745,6 +771,81 @@ int gui_bridge_post_uasync_owned_fn(void (*fn)(void*), void* arg) { return post_command(fn, arg, true); } +/* Копия команды и её post принимаются одной операцией с привязкой к живому ядру. */ +int gui_bridge_member_files_request(uint64_t group, uint64_t server, const struct mf_message* message) { + if (!group || !server || !message || !message->request_id || message->command < MF_LIST || message->command > MF_QUOTA || + message->data_len > MF_BLOCK_SIZE || strnlen(message->path, sizeof(message->path)) > MF_PATH_MAX || + strnlen(message->target, sizeof(message->target)) > MF_PATH_MAX) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: invalid request group=%llu server=%llu", + (unsigned long long)group, (unsigned long long)server); return -1; + } + std::lock_guard guard(bridge_mutex); + if (!g_receiver || !g_inst || !g_ua || !uasync_is_running(g_ua)) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file bridge: request rejected, core unavailable id=%llu command=%s", + (unsigned long long)message->request_id, mf_command_text(message->command)); return -1; + } + auto* request = static_cast(u_calloc(1, sizeof(mf_gui_request))); + if (!request) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: request snapshot allocation failed id=%llu", + (unsigned long long)message->request_id); return -1; + } + request->inst = g_inst; request->group = group; request->server = server; request->message = *message; + if (post_command(member_files_gui_request, request, true, cancel_member_files_request) < 0) { + u_free(request); DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: request queue rejected id=%llu", + (unsigned long long)message->request_id); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "file bridge: request queued id=%llu command=%s group=%llu server=%llu owner=%llu path=%s", + (unsigned long long)message->request_id, mf_command_text(message->command), (unsigned long long)group, + (unsigned long long)server, (unsigned long long)message->owner, message->path); + return 0; +} + +/* Поле inst входа не используется: GUI не получает указатель, способный устареть до enqueue. */ +int gui_bridge_member_files_transfer(const struct mf_transfer_request* source) { + if (!source || !source->id || !source->group || !source->server || !source->owner || !mf_path_valid(source->remote, 0) || + !source->local[0] || strnlen(source->local, sizeof(source->local)) > MF_PATH_MAX) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: invalid transfer snapshot"); return -1; + } + std::lock_guard guard(bridge_mutex); + if (!g_receiver || !g_inst || !g_ua || !uasync_is_running(g_ua)) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file bridge: transfer rejected, core unavailable id=%llu", + (unsigned long long)source->id); return -1; + } + auto* request = static_cast(u_calloc(1, sizeof(mf_transfer_request))); + if (!request) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: transfer snapshot allocation failed id=%llu", + (unsigned long long)source->id); return -1; + } + *request = *source; request->inst = g_inst; + if (post_command(member_files_gui_transfer, request, true, cancel_member_files_transfer) < 0) { + u_free(request); DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: transfer queue rejected id=%llu", + (unsigned long long)source->id); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "file bridge: transfer queued id=%llu group=%llu server=%llu owner=%llu upload=%d remote=%s local=%s", + (unsigned long long)source->id, (unsigned long long)source->group, (unsigned long long)source->server, + (unsigned long long)source->owner, source->upload, source->remote, source->local); + return 0; +} + +/* После исполнения отменяет native handle; при detach до исполнения payload просто освобождается. */ +int gui_bridge_member_files_cancel(uint64_t id) { + std::lock_guard guard(bridge_mutex); + if (!id || !g_inst || !g_ua || !uasync_is_running(g_ua)) { + DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "file bridge: cancel rejected id=%llu core_available=%d", + (unsigned long long)id, g_inst && g_ua && uasync_is_running(g_ua)); return -1; + } + auto* request = static_cast(u_calloc(1, sizeof(mf_cancel_request))); + if (!request) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: cancel snapshot allocation failed id=%llu", (unsigned long long)id); return -1; + } + request->inst = g_inst; request->id = id; + if (post_command(member_files_gui_cancel, request, true) < 0) { + u_free(request); DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "file bridge: cancel queue rejected id=%llu", (unsigned long long)id); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "file bridge: cancel queued id=%llu", (unsigned long long)id); + return 0; +} + /* Сериализация и публикация образуют одну границу владения с detach ядра. */ int gui_bridge_post_setting(enum gui_setting_target target, const char* key, const char* value) { if ((target != GUI_SETTING_UI_STATE && target != GUI_SETTING_CHAT) || !key || !key[0] || !value) {