From 9dd2dffc80c28e63aee3b2af76504e7d7dd8e17e Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 4 Oct 2026 15:58:54 +0200 Subject: [PATCH] Expose live radio subscriptions in member updates --- src/chat/chat_member.c | 25 +++++++++++++++++++++---- src/chat/chat_member.h | 7 ++++--- src/routing_layer/topo_group.c | 9 ++++++++- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/chat/chat_member.c b/src/chat/chat_member.c index 977d34c2..ab1d57a5 100644 --- a/src/chat/chat_member.c +++ b/src/chat/chat_member.c @@ -2,7 +2,7 @@ * chat_member.c — единый код запроса мемберов для GUI * * Используется: chatgui (desktop), headless CLI, Android. - * Все три потребителя получают данные через chat_core_get_member_list(). + * Live-состояние участников читается только в uasync-потоке. */ #include "chat_core_priv.h" @@ -32,6 +32,16 @@ int chat_core_member_online(struct UTUN_INSTANCE* inst, const char* ch_id, uint6 return topo_node_find_by_id(g, node_id) != NULL; } +/* Рация — живая подписка на конкретную CHAT-группу; у self нет записи в group->nodes. */ +static uint8_t member_radio_flag(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id) { + if (!inst || !inst->topo_groups || !ch_id || !ch_id[0]) return 0; + struct TOPO_GROUP* group = topo_groups_find(inst->topo_groups, strtoull(ch_id, NULL, 10)); + if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT) return 0; + if (node_id == inst->node_id) return group->radio_active ? CHAT_MEMBER_FLAG_RADIO : 0; + struct TOPO_GROUP_NODE* node = topo_node_find_by_id(group, node_id); + return node && node->radio ? CHAT_MEMBER_FLAG_RADIO : 0; +} + /* ─── парсинг adm_tags в flags ─── */ static uint8_t parse_adm_tags_flags(const char* adm_tags) { @@ -138,7 +148,7 @@ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uin const char* adm_tags = (const char*)sqlite3_column_text(st, 3); m->storage = (uint8_t)sqlite3_column_int(st, 4); m->is_self = (uint8_t)(m->node_id == my_id ? 1 : 0); - m->flags = parse_adm_tags_flags(adm_tags); + m->flags = parse_adm_tags_flags(adm_tags) | member_radio_flag(inst, ch_id, m->node_id); if (sqlite3_column_int(st, 5)) m->flags |= CHAT_MEMBER_FLAG_DELETED; m->rtt = 0xFFFF; m->nat_type = 0xFF; @@ -253,7 +263,7 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u const char* adm_tags = (const char*)sqlite3_column_text(st, 2); m.storage = (uint8_t)sqlite3_column_int(st, 3); m.is_self = (uint8_t)(node_id == my_id ? 1 : 0); - m.flags = parse_adm_tags_flags(adm_tags); + m.flags = parse_adm_tags_flags(adm_tags) | member_radio_flag(inst, ch_id, node_id); if (sqlite3_column_int(st, 4)) m.flags |= CHAT_MEMBER_FLAG_DELETED; m.rtt = 0xFFFF; m.nat_type = 0xFF; @@ -374,8 +384,15 @@ static void on_member_props_changed(uint64_t node_id, const char* adm_tags, cons evt[0] = (uint8_t)cl; memcpy(evt + 1, channel_id, cl); evt[1 + cl] = 1; - if (chat_core_get_single_member(inst, channel_id, node_id, evt + 1 + cl + 1) == 0) + if (chat_core_get_single_member(inst, channel_id, node_id, evt + 1 + cl + 1) == 0) { + const uint8_t* member = evt + 1 + cl + 1; + DEBUG_DEBUG(DEBUG_CATEGORY_CHAT_SYNC, "chat_member: update ch=%s node=%016llx online=%u self=%u radio=%u", + channel_id, (unsigned long long)node_id, member[8], member[9], !!(member[10] & CHAT_MEMBER_FLAG_RADIO)); chat_event_post(inst, CHAT_EVT_MEMBER_UPDATED, evt, 1 + (int)cl + 1 + CHAT_MEMBER_DISPLAY_SIZE); + } else { + DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC, "chat_member: cannot build update ch=%s node=%016llx", + channel_id, (unsigned long long)node_id); + } return; } sqlite3_finalize(st); diff --git a/src/chat/chat_member.h b/src/chat/chat_member.h index 9770816b..cacd2b67 100644 --- a/src/chat/chat_member.h +++ b/src/chat/chat_member.h @@ -2,14 +2,14 @@ * chat_member.h — единая структура мембера для отображения в GUI * * Используется chatgui (desktop), headless CLI, Android. - * Все три потребителя получают данные через chat_core_get_member_list(), - * а не прямыми SQL-запросами. + * Live-снимки для всех потребителей формирует chat_core_get_member_list() в uasync-потоке. + * Android дополняет ими свой JSON-список с расширенными свойствами участников. * * Формат на проводе (ручная сериализация, фиксированный размер 79 байт): * [0-7] node_id uint64_t LE * [8] online uint8_t * [9] is_self uint8_t - * [10] flags uint8_t 0x01=supernode 0x02=admin 0x04=moder + * [10] flags uint8_t 0x01=supernode 0x02=admin 0x04=moder 0x08=deleted 0x10=radio * [11-12] rtt uint16_t LE (ms, 0xFFFF=нет данных) * [13] nat_type uint8_t ADDR_TYPE_* * [14] storage uint8_t @@ -30,6 +30,7 @@ extern "C" { #define CHAT_MEMBER_FLAG_ADMIN 0x02 #define CHAT_MEMBER_FLAG_MODER 0x04 #define CHAT_MEMBER_FLAG_DELETED 0x08 +#define CHAT_MEMBER_FLAG_RADIO 0x10 #define CHAT_MEMBER_DISPLAY_SIZE 79 diff --git a/src/routing_layer/topo_group.c b/src/routing_layer/topo_group.c index babcc5b7..811c898e 100644 --- a/src/routing_layer/topo_group.c +++ b/src/routing_layer/topo_group.c @@ -896,7 +896,14 @@ void topo_group_set_radio(struct TOPO_GROUP* group, int on) { group->radio_active = v; DEBUG_INFO(DEBUG_CATEGORY_BGP, "set_radio: %s grp=%016llx ch=%s", v ? "ON" : "OFF", (unsigned long long)group->group_id, group->channel_id); - topo_group_update_my_nodeinfo(group->instance, group); + if (topo_group_update_my_nodeinfo(group->instance, group) < 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "set_radio: cannot update local NODEINFO grp=%016llx ch=%s radio=%u", + (unsigned long long)group->group_id, group->channel_id, v); + return; + } + /* Свой мембер обновляется через те же подписки, что и удалённые BGP-узлы. */ + for (struct topo_node_cbk_entry* cb = group->node_cbks; cb; cb = cb->next) + cb->fn(group, group->instance->node_id, TOPO_NODE_EVENT_UPDATE, cb->arg); if (group->senders_list) { struct ll_entry* se = group->senders_list->head; while (se) {