Centralize owned settings and name requests in the GUI bridge

This commit is contained in:
evgeny committed 2026-10-27 17:27:04 +01:00
1 parent 05b0ef50e4
commit f1f5d33788
3 files changed
+74 -1

No files matched your search

+1 -1
View File
@@ -143,7 +143,7 @@ class SettingsSaveTest : public QObject {
QCOMPARE(VoiceEncoder::preset(), 2);
}
if (kind == Dialog) {
QCOMPARE(QString::fromUtf8(core.inst->config->global.my_node_name), QString("After"));
QCOMPARE(QString::fromUtf8(core.inst->name), QString("After"));
QCOMPARE(chat_setting_get_int(core.inst, "stream_subblocks", -1), 12);
QCOMPARE(chat_setting_get_int(core.inst, "stream_rs_redundant", -1), 3);
QCOMPARE(core.db.getUiStateInt("join_policy", -1), 1);
+12
View File
@@ -92,6 +92,18 @@ void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg);
/* Передать отдельный u_malloc/u_calloc-блок arg: 0 — мост принял владение, callback обязан освободить arg;
* отмена до callback освобождает его мостом. -1 — arg остаётся вызывающему. Вложенные ресурсы не освобождаются. */
int gui_bridge_post_uasync_owned_fn(void (*fn)(void*), void* arg);
/* Назначение пары key/value: локальный ui_state SQLite либо live chat_setting ядра. */
enum gui_setting_target { GUI_SETTING_UI_STATE, GUI_SETTING_CHAT };
/* Скопировать NUL-строки key/value в собственный запрос и поставить его текущему ядру под mutex моста.
* key непустой; key+NUL+value+NUL должны помещаться в data соответствующего запроса ядра.
* 0 — принято (не подтверждение записи/валидации ядром), -1 — отказ с логом, память за вызывающим не остаётся.
* При отмене запрос освобождает мост, после исполнения — trampoline. INI-файл эта функция не меняет. */
int gui_bridge_post_setting(enum gui_setting_target target, const char* key, const char* value);
/* Скопировать name (включая пустое, в пределах update_my_name_arg::name) и поставить обновление текущему ядру.
* Владение и результат — как у post_setting; строка/форма вызывающего после возврата больше не нужны. */
int gui_bridge_update_name(const char* name);
/* Composer-запрос: 0 — владение req передано, callback/отмена освободят блок; -1 — req остаётся вызывающему. */
struct chat_msg_submit;
int gui_bridge_submit_message(struct chat_msg_submit* req);
@@ -625,6 +625,67 @@ int gui_bridge_post_uasync_owned_fn(void (*fn)(void*), void* arg) {
return post_command(fn, arg, true);
}
/* Сериализация и публикация образуют одну границу владения с 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) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting rejected: target=%d missing/invalid key or value", target); return -1;
}
const size_t capacity = target == GUI_SETTING_UI_STATE ? sizeof(save_ui_state_arg::data) : sizeof(chat_setting_arg::data);
size_t keyLength = 0, valueLength = 0;
while (keyLength < capacity && key[keyLength]) ++keyLength;
while (valueLength < capacity && value[valueLength]) ++valueLength;
if (keyLength + valueLength + 2 > capacity) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting rejected: target=%d key_bytes=%zu value_bytes=%zu capacity=%zu",
target, keyLength, valueLength, capacity);
return -1;
}
std::lock_guard<std::mutex> guard(bridge_mutex);
if (!g_inst || !g_ua) {
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "GUI setting rejected: target=%d key=%s core unavailable", target, key); return -1;
}
const size_t bytes = target == GUI_SETTING_UI_STATE ? sizeof(save_ui_state_arg) : sizeof(chat_setting_arg);
void* request = u_malloc(bytes);
if (!request) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting allocation failed: target=%d key=%s bytes=%zu", target, key, bytes); return -1;
}
char* data;
void (*callback)(void*);
if (target == GUI_SETTING_UI_STATE) {
auto* arg = static_cast<save_ui_state_arg*>(request);
arg->inst = g_inst; data = arg->data; callback = chat_core_save_ui_state_trampoline;
} else {
auto* arg = static_cast<chat_setting_arg*>(request);
arg->inst = g_inst; data = arg->data; callback = chat_core_set_setting_trampoline;
}
memcpy(data, key, keyLength + 1); memcpy(data + keyLength + 1, value, valueLength + 1);
if (post_command(callback, request, true) < 0) {
u_free(request);
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting queue rejected: target=%d key=%s", target, key); return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "GUI setting queued: target=%d key=%s value_bytes=%zu", target, key, valueLength);
return 0;
}
/* Смена имени использует тот же owned lifecycle без передачи instance из GUI. */
int gui_bridge_update_name(const char* name) {
if (!name) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name rejected: missing name"); return -1; }
size_t length = 0;
while (length < sizeof(update_my_name_arg::name) && name[length]) ++length;
if (length == sizeof(update_my_name_arg::name)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name rejected: name exceeds request capacity"); return -1;
}
std::lock_guard<std::mutex> guard(bridge_mutex);
if (!g_inst || !g_ua) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "GUI name rejected: core unavailable"); return -1; }
auto* request = static_cast<update_my_name_arg*>(u_malloc(sizeof(update_my_name_arg)));
if (!request) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name allocation failed"); return -1; }
request->inst = g_inst; memcpy(request->name, name, length + 1);
if (post_command(chat_core_update_my_name_trampoline, request, true) < 0) {
u_free(request); DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name queue rejected"); return -1;
}
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "GUI name queued: bytes=%zu", length);
return 0;
}
int gui_bridge_submit_message(struct chat_msg_submit* req) {
std::lock_guard<std::mutex> guard(bridge_mutex);
if (!req || !g_inst || !g_ua || !uasync_is_running(g_ua)) {