fix(chat): commit UI preferences before reporting or broadcasting them
This commit is contained in:
1 parent
5360453960
commit
fec2cecdb3
3 files changed
+89
-39
No files matched your search
+30
-20
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user