fix(chat): commit radio mute before applying cache and reporting success

This commit is contained in:
evgeny committed 2026-10-27 05:24:47 +01:00
1 parent 32a8c1c48b
commit 1bb824fb8a
3 files changed
+49 -23

No files matched your search

+5 -4
View File
@@ -274,14 +274,15 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u
int chat_core_toggle_member_radio_mute(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id) {
uint8_t member[CHAT_MEMBER_DISPLAY_SIZE];
if (!ch_id || !ch_id[0] || strlen(ch_id) >= 64 || chat_core_get_single_member(inst, ch_id, node_id, member) < 0) {
if (!chat_channel_id(ch_id) || chat_core_get_single_member(inst, ch_id, node_id, member) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio: mute toggle member not found node=%016llx", (unsigned long long)node_id); return -1;
}
int muted = !(member[10] & CHAT_MEMBER_FLAG_RADIO_MUTED);
if (radio_set_member_muted(inst, strtoull(ch_id, NULL, 10), node_id, muted) < 0) return -1;
if (chat_core_get_single_member(inst, ch_id, node_id, member) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: cannot publish local mute ch=%s node=%016llx",
ch_id, (unsigned long long)node_id); return -1;
/* Снимок уже готов: после committed mute нет нового чтения, способного превратить успех в ошибку. */
member[10] = (uint8_t)((member[10] & ~CHAT_MEMBER_FLAG_RADIO_MUTED) | (muted ? CHAT_MEMBER_FLAG_RADIO_MUTED : 0));
if (!inst->topo_groups || !topo_groups_find(inst->topo_groups, strtoull(ch_id, NULL, 10))) {
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio: mute committed, group removed by audio callback ch=%s", ch_id); return 0;
}
size_t cl = strlen(ch_id);
uint8_t evt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE];
+40 -19
View File
@@ -478,9 +478,10 @@ static int radio_load_muted_members(struct radio_ctx* ctx) {
}
members = next; members[count++] = (uint64_t)sqlite3_column_int64(st, 0);
}
sqlite3_finalize(st);
if (rc != SQLITE_DONE) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: read muted members: %s", sqlite3_errmsg(db)); u_free(members); return -1;
int final = sqlite3_finalize(st);
if (rc != SQLITE_DONE || final != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: read muted members rc=%d finalize=%d: %s", rc, final, sqlite3_errmsg(db));
u_free(members); return -1;
}
u_free(ctx->muted_members); ctx->muted_members = members; ctx->muted_count = count; ctx->muted_loaded = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "radio: loaded local mutes grp=%016llx count=%d",
@@ -524,34 +525,54 @@ int radio_set_member_muted(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio: invalid local mute grp=%016llx node=%016llx muted=%d",
(unsigned long long)group_id, (unsigned long long)node_id, muted); return -1;
}
if (!ctx->muted_loaded && radio_load_muted_members(ctx) < 0) return -1;
int slot = -1;
sqlite3* db = inst->topo_sqlite_db;
if (!db || !sqlite3_get_autocommit(db)) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: local mute requires database without external transaction grp=%016llx",
(unsigned long long)group_id); return -1;
}
int rc = sqlite3_exec(db, "BEGIN IMMEDIATE /* radio mute */", NULL, NULL, NULL);
if (rc != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: local mute begin grp=%016llx rc=%d: %s",
(unsigned long long)group_id, rc, sqlite3_errmsg(db)); return -1;
}
sqlite3_stmt* st = NULL; const char* stage = "load cache"; int slot = -1;
if (!ctx->muted_loaded && radio_load_muted_members(ctx) < 0) { rc = SQLITE_ERROR; goto error; }
for (int i = 0; i < ctx->muted_count; i++) if (ctx->muted_members[i] == node_id) { slot = i; break; }
/* Выделить память до записи БД: после commit применение не может отказать. */
if (muted && slot < 0) {
uint64_t* next = u_realloc(ctx->muted_members, (size_t)(ctx->muted_count + 1) * sizeof(*next));
if (!next) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: grow muted member cache failed"); return -1; }
if (!next) { stage = "reserve cache"; rc = SQLITE_NOMEM; goto error; }
ctx->muted_members = next;
}
sqlite3* db = inst->topo_sqlite_db;
char sql[160];
snprintf(sql, sizeof(sql), "UPDATE \"peers_%llu\" SET radio_muted=? WHERE node_id=?", (unsigned long long)group_id);
sqlite3_stmt* st = NULL;
if (!db || sqlite3_prepare_v2(db, sql, -1, &st, NULL) != SQLITE_OK) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: prepare local mute: %s", db ? sqlite3_errmsg(db) : "no database"); return -1;
}
sqlite3_bind_int(st, 1, muted); sqlite3_bind_int64(st, 2, (sqlite3_int64)node_id);
int rc = sqlite3_step(st); sqlite3_finalize(st);
if (rc != SQLITE_DONE || sqlite3_changes(db) != 1) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: save local mute grp=%016llx node=%016llx rc=%d changed=%d: %s",
(unsigned long long)group_id, (unsigned long long)node_id, rc, sqlite3_changes(db), sqlite3_errmsg(db)); return -1;
}
stage = "prepare"; rc = sqlite3_prepare_v2(db, sql, -1, &st, NULL); if (rc != SQLITE_OK) goto error;
stage = "bind mute"; rc = sqlite3_bind_int(st, 1, muted); if (rc != SQLITE_OK) goto error;
stage = "bind member"; rc = sqlite3_bind_int64(st, 2, (sqlite3_int64)node_id); if (rc != SQLITE_OK) goto error;
stage = "step"; rc = sqlite3_step(st); if (rc != SQLITE_DONE) goto error;
stage = "changes"; if (sqlite3_changes(db) != 1) { rc = SQLITE_CONSTRAINT; goto error; }
stage = "finalize"; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto error;
stage = "commit"; rc = sqlite3_exec(db, "COMMIT /* radio mute */", NULL, NULL, NULL); if (rc != SQLITE_OK) goto error;
if (muted && slot < 0) ctx->muted_members[ctx->muted_count++] = node_id;
else if (!muted && slot >= 0) ctx->muted_members[slot] = ctx->muted_members[--ctx->muted_count];
radio_refresh_member_mute(inst, group_id, node_id);
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "radio: local member mute grp=%016llx node=%016llx muted=%d",
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "radio: local member mute grp=%016llx node=%016llx muted=%d committed=1",
(unsigned long long)group_id, (unsigned long long)node_id, muted);
radio_refresh_member_mute(inst, group_id, node_id);
return 0;
error:
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: local mute grp=%016llx node=%016llx muted=%d stage=%s rc=%d: %s",
(unsigned long long)group_id, (unsigned long long)node_id, muted, stage, rc, sqlite3_errmsg(db));
rc = sqlite3_finalize(st);
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: local mute cleanup finalize rc=%d", rc);
if (!sqlite3_get_autocommit(db)) {
rc = sqlite3_exec(db, "ROLLBACK", NULL, NULL, NULL);
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: local mute rollback rc=%d: %s", rc, sqlite3_errmsg(db));
if (!sqlite3_get_autocommit(db)) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: local mute transaction remains open; requesting owner stop/DB close");
utun_instance_stop(inst);
}
}
return -1;
}
/* ── подписка (вкл/выкл прослушивание) ── */
+4
View File
@@ -39,6 +39,10 @@ int radio_member_muted(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t n
int radio_member_silenced(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t node_id);
/* Изменилось временное подавление; настройки в БД и подписка остаются прежними. */
void radio_refresh_member_mute(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t node_id);
/* Сохранить muted=0/1 для другого участника существующей CHAT-группы;0 — committed,-1 — отказ.
* Собственная BEGIN IMMEDIATE,чужая транзакция запрещена. Память резервируется до записи;
* cache и mute callback меняются после commit. При отказе настройка/звук сохраняются;
* неудачный rollback с открытой транзакцией запрашивает stop/закрытие БД владельцем. */
int radio_set_member_muted(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t node_id, int muted);
/* После удаления записи участника из БД удалить её из локального кэша. */
void radio_forget_member_mute(struct UTUN_INSTANCE* inst, uint64_t group_id, uint64_t node_id);