fix(chat): commit UI preferences before reporting or broadcasting them

This commit is contained in:
evgeny committed 2026-10-27 07:24:30 +01:00
1 parent 5360453960
commit fec2cecdb3
3 files changed
+89 -39

No files matched your search

+30 -20
View File
@@ -279,51 +279,61 @@ int chat_core_is_initialized(struct UTUN_INSTANCE* inst) { return (inst && CC(in
/* ─── chat settings (GUI → uasync) ─── */
void chat_core_set_setting(struct UTUN_INSTANCE* inst, const char* name, const char* value) {
chat_setting_set(inst, name, value);
if (name && strcmp(name, "group_autoconnect") == 0) chat_core_apply_group_autoconnect(inst);
int chat_core_set_setting(struct UTUN_INSTANCE* inst, const char* name, const char* value) {
int rc = chat_setting_set(inst, name, value);
if (!rc && !strcmp(name, "group_autoconnect")) chat_core_apply_group_autoconnect(inst);
return rc;
}
void chat_core_set_setting_trampoline(void* arg) {
struct chat_setting_arg* a = (struct chat_setting_arg*)arg;
const char* name = a->data;
const char* value = name + strlen(name) + 1;
chat_core_set_setting(a->inst, name, value);
if (!a) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "chat setting: null request"); return; }
const char* end = memchr(a->data, 0, sizeof(a->data));
if (!end || end == a->data + sizeof(a->data) - 1 || !memchr(end + 1, 0, sizeof(a->data) - (size_t)(end + 1 - a->data)))
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "chat setting: unterminated name/value request");
else chat_core_set_setting(a->inst, a->data, end + 1);
u_free(arg);
}
/* ─── per-channel «play sound on new message» ─── */
int chat_core_get_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !cc->initialized || !ch_id || !ch_id[0]) return 1;
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
if (!cc || !cc->initialized || !cc->db || !chat_channel_id(ch_id)) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "sound setting: invalid read context/channel"); return -1;
}
char key[128]; snprintf(key, sizeof(key), "ch_%s_sound_on_message", ch_id);
sqlite3_stmt* st = NULL;
if (sqlite3_prepare_v2(cc->db, "SELECT value FROM ui_state WHERE key=?", -1, &st, NULL) != SQLITE_OK) return 1;
sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
int on = 1;
if (sqlite3_step(st) == SQLITE_ROW) {
int rc = sqlite3_prepare_v2(cc->db, "SELECT value FROM ui_state WHERE key=?", -1, &st, NULL), on = 1;
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
if (rc == SQLITE_OK) rc = sqlite3_step(st);
if (rc == SQLITE_ROW) {
const char* v = (const char*)sqlite3_column_text(st, 0);
if (v) on = atoi(v) != 0;
if (sqlite3_column_type(st, 0) != SQLITE_TEXT || sqlite3_column_bytes(st, 0) != 1 || !v || (*v != '0' && *v != '1')) rc = SQLITE_CORRUPT;
else on = *v == '1';
}
int final = sqlite3_finalize(st);
if ((rc != SQLITE_ROW && rc != SQLITE_DONE) || final != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "sound setting: read ch=%s rc=%d finalize=%d: %s", ch_id, rc, final, sqlite3_errmsg(cc->db)); return -1;
}
sqlite3_finalize(st);
return on;
}
void chat_core_set_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id, int on) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !cc->initialized || !ch_id || !ch_id[0]) return;
int chat_core_set_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id, int on) {
if (!chat_channel_id(ch_id)) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "sound setting: invalid channel"); return -1; }
char key[128]; snprintf(key, sizeof(key), "ch_%s_sound_on_message", ch_id);
char val[2]; snprintf(val, sizeof(val), "%d", on ? 1 : 0);
chat_core_save_ui_state(inst, key, val);
if (chat_core_save_ui_state(inst, key, val)) return -1;
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: sound_on_message ch=%s = %d", CC_ID, ch_id, on ? 1 : 0);
chat_sync_broadcast_sound_setting(inst, ch_id, on ? 1 : 0);
return 0;
}
void chat_core_set_sound_on_message_trampoline(void* arg) {
struct chat_sound_arg* a = (struct chat_sound_arg*)arg;
if (!a) return;
chat_core_set_sound_on_message(a->inst, a->ch_id, a->on);
if (!a) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "sound setting: null request"); return; }
if (!memchr(a->ch_id, 0, sizeof(a->ch_id))) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "sound setting: unterminated channel request");
else chat_core_set_sound_on_message(a->inst, a->ch_id, a->on);
u_free(arg);
}
+9 -5
View File
@@ -190,14 +190,18 @@ void chat_core_sync_my_addresses(struct UTUN_INSTANCE* inst);
* Адреса обновляются отдельно через topo_node; подпись member-блока их не содержит. */
void chat_core_update_my_member(struct UTUN_INSTANCE* inst);
// Сохранение key-value в ui_state (вызывается из uasync-потока)
void chat_core_save_ui_state(struct UTUN_INSTANCE* inst, const char* key, const char* value);
/* Сохранить key/value в uasync:0 — committed/no-op,-1 — отказ;key непустой,value допускает пустую строку.
* Собственная транзакция проверяет все SQL-этапы;внешняя запрещена,no-op не вызывает commit hook.
* Ошибка сохраняет прежнее значение;unrecovered rollback запрашивает owner stop/DB close. */
int chat_core_save_ui_state(struct UTUN_INSTANCE* inst, const char* key, const char* value);
/* Per-channel «play sound on new message» (ui_state: ch_<id>_sound_on_message, default 1=on).
* set — сохраняет и рассылает пирам через chat_sync (троттлинг db_sync на сервере). */
* get возвращает0/1,-1 при ошибке;отсутствие записи —1. set возвращает0/-1,
* после собственного commit/no-op рассылает желаемое значение через chat_sync best effort.
* SQL-отказ не рассылается,идентичный повтор повторяет доставку. ch_id — канонический ID канала. */
int chat_core_get_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id);
void chat_core_set_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id, int on);
int chat_core_set_sound_on_message(struct UTUN_INSTANCE* inst, const char* ch_id, int on);
struct chat_sound_arg { struct UTUN_INSTANCE* inst; char ch_id[64]; int on; };
void chat_core_set_sound_on_message_trampoline(void* arg);
@@ -206,7 +210,7 @@ struct save_ui_state_arg { struct UTUN_INSTANCE* inst; char data[1024]; };
void chat_core_save_ui_state_trampoline(void* arg);
// Установка chat-настройки (name=value) через chat_setting API (GUI → uasync)
void chat_core_set_setting(struct UTUN_INSTANCE* inst, const char* name, const char* value);
int chat_core_set_setting(struct UTUN_INSTANCE* inst, const char* name, const char* value);
struct chat_setting_arg { struct UTUN_INSTANCE* inst; char data[256]; };
void chat_core_set_setting_trampoline(void* arg);
+50 -14
View File
@@ -375,24 +375,60 @@ void chat_core_sync_my_addresses(struct UTUN_INSTANCE* inst) {
chat_core_update_my_member(inst);
}
void chat_core_save_ui_state(struct UTUN_INSTANCE* inst, const char* key, const char* value) {
struct chat_core_ctx* cc = CC(inst);
if (!cc || !cc->initialized || !key || !value) return;
sqlite3_stmt* st = NULL;
sqlite3_prepare_v2(cc->db,
"INSERT OR REPLACE INTO ui_state(key, value) VALUES(?, ?)", -1, &st, NULL);
if (st) {
sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
sqlite3_bind_text(st, 2, value, -1, SQLITE_STATIC);
sqlite3_step(st); sqlite3_finalize(st);
int chat_core_save_ui_state(struct UTUN_INSTANCE* inst, const char* key, const char* value) {
struct chat_core_ctx* cc = inst ? CC(inst) : NULL;
if (!cc || !cc->initialized || !cc->db || !key || !key[0] || !value || strlen(key) > INT_MAX || strlen(value) > INT_MAX ||
!sqlite3_get_autocommit(cc->db)) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: invalid context/key/value or outer transaction"); return -1;
}
int rc = sqlite3_exec(cc->db, "BEGIN IMMEDIATE /* ui_state */", NULL, NULL, NULL);
if (rc != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: begin rc=%d: %s", rc, sqlite3_errmsg(cc->db)); return -1; }
sqlite3_stmt* st = NULL; const char* stage = "prepare read"; int result = -1, same = 0;
rc = sqlite3_prepare_v2(cc->db, "SELECT value FROM ui_state WHERE key=?", -1, &st, NULL);
if (rc == SQLITE_OK) { stage = "bind read"; rc = sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC); }
if (rc == SQLITE_OK) { stage = "step read"; rc = sqlite3_step(st); }
if (rc == SQLITE_ROW) {
int type = sqlite3_column_type(st, 0), bytes = sqlite3_column_bytes(st, 0);
const char* text = (const char*)sqlite3_column_text(st, 0);
if (type != SQLITE_NULL && (type != SQLITE_TEXT || !text || strlen(text) != (size_t)bytes)) {
stage = "stored value"; rc = SQLITE_CORRUPT; goto error;
}
same = text && !strcmp(text, value);
} else if (rc != SQLITE_DONE) goto error;
stage = "finalize read"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
if (same) { DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "UI state: unchanged key=%s", key); result = 0; goto done; }
stage = "prepare write";
rc = sqlite3_prepare_v2(cc->db, "INSERT INTO ui_state(key,value) VALUES(?1,?2) ON CONFLICT(key) DO UPDATE SET value=excluded.value", -1, &st, NULL);
if (rc == SQLITE_OK) { stage = "bind key"; rc = sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC); }
if (rc == SQLITE_OK) { stage = "bind value"; rc = sqlite3_bind_text(st, 2, value, -1, SQLITE_STATIC); }
if (rc == SQLITE_OK) { stage = "step write"; rc = sqlite3_step(st); }
if (rc != SQLITE_DONE) goto error;
if (sqlite3_changes(cc->db) != 1) { stage = "changes != 1"; rc = SQLITE_CONSTRAINT; goto error; }
stage = "finalize write"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
stage = "commit"; rc = sqlite3_exec(cc->db, "COMMIT /* ui_state */", NULL, NULL, NULL); if (rc != SQLITE_OK) goto error;
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "UI state: committed key=%s bytes=%zu", key, strlen(value)); return 0;
error:
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: key=%s stage=%s rc=%d: %s", key, stage, rc, sqlite3_errmsg(cc->db));
done:
rc = sqlite3_finalize(st);
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: cleanup finalize rc=%d", rc);
if (!sqlite3_get_autocommit(cc->db)) {
rc = sqlite3_exec(cc->db, "ROLLBACK", NULL, NULL, NULL);
if (rc != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: rollback rc=%d: %s", rc, sqlite3_errmsg(cc->db)); result = -1; }
if (!sqlite3_get_autocommit(cc->db)) {
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: transaction remains open;requesting owner stop/DB close");
utun_instance_stop(inst); result = -1;
}
}
return result;
}
void chat_core_save_ui_state_trampoline(void* arg) {
struct save_ui_state_arg* a = (struct save_ui_state_arg*)arg;
if (!a) return;
const char* key = a->data;
const char* value = key + strlen(key) + 1;
chat_core_save_ui_state(a->inst, key, value);
if (!a) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: null request"); return; }
const char* end = memchr(a->data, 0, sizeof(a->data));
if (!end || end == a->data + sizeof(a->data) - 1 || !memchr(end + 1, 0, sizeof(a->data) - (size_t)(end + 1 - a->data)))
DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "UI state: unterminated key/value request");
else chat_core_save_ui_state(a->inst, a->data, end + 1);
u_free(arg);
}