chat_settings: [chat] config section + live sync + fix test segfault

- New chat_setting.c/h: unified chat settings API (int/str getters)
- Config parser supports [chat] section, calls chat_setting_set per key
- chat_core exposes chat_core_set_setting / trampoline for GUI bridge
- chatgui: AudioDevice + Storage pages persist to [chat] config + live apply
- chatgui-android: ConfigProvider, NativeLib, headless handle chat settings
- MessageList: 200 msg limit, fixed saveChannelState via trampoline
- test_media_delivery_full: fix segfault at shutdown (mon() timer vs freed conns)
This commit is contained in:
evgeny committed 2026-07-29 21:18:49 +03:00
1 parent efbb495016
commit b8920cf483
22 files changed
+420 -43

No files matched your search

+2
View File
@@ -63,6 +63,7 @@ utun_CORE_SOURCES = \
lwip_tcp/lwip_tcp_in.c \
lwip_tcp/lwip_tcp_out.c \
chat/chat_event.c \
chat/chat_setting.c \
chat/chat_core.c \
chat/chat_msg.c \
chat/chat_channel.c \
@@ -133,6 +134,7 @@ libutun_a_SOURCES = \
lwip_tcp/lwip_tcp_in.c \
lwip_tcp/lwip_tcp_out.c \
chat/chat_event.c \
chat/chat_setting.c \
chat/chat_core.c \
chat/chat_msg.c \
chat/chat_channel.c \
+16
View File
@@ -12,6 +12,7 @@
#include "chat_core_priv.h"
#include "chat_event.h"
#include "chat_setting.h"
#include "../utun_instance.h"
#include "../ntp_time.h"
@@ -239,3 +240,18 @@ void chat_core_destroy(struct UTUN_INSTANCE* inst) {
sqlite3* chat_core_get_db(void) { return g_cc.db; }
struct UTUN_INSTANCE* chat_core_get_inst(void) { return g_cc.inst; }
int chat_core_is_initialized(void) { return g_cc.initialized; }
/* ─── chat settings (GUI → uasync) ─── */
void chat_core_set_setting(const char* name, const char* value) {
chat_setting_set(name, value);
}
struct chat_setting_arg { char data[256]; };
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(name, value);
u_free(arg);
}
+4
View File
@@ -76,6 +76,10 @@ void chat_core_sync_my_addresses(void);
void chat_core_save_ui_state(const char* key, const char* value);
void chat_core_save_ui_state_trampoline(void* arg);
/* Установка chat-настройки (name=value) через chat_setting API (GUI → uasync) */
void chat_core_set_setting(const char* name, const char* value);
void chat_core_set_setting_trampoline(void* arg);
/* Запуск подключения к узлам выбранного канала (GUI → uasync) */
void chat_core_connect_channel(const char* ch_id);
void chat_core_connect_channel_trampoline(void* arg);
+127
View File
@@ -0,0 +1,127 @@
/*
* chat_setting.c — chat-related settings: validation, storage, on-the-fly apply
*
* Используется и из парсера конфига (headless), и из GUI (через bridge).
* Никаких зависимостей от g_cc или instance — работает автономно.
*/
#include "chat_setting.h"
#include "../../lib/debug_config.h"
#ifdef DEBUG_CATEGORY_DB_SYNC
#define CS_ID DEBUG_CATEGORY_DB_SYNC
#else
#define CS_ID DEBUG_CATEGORY_GENERAL
#endif
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* ─── типы настроек ─── */
#define CHAT_SETTING_INT 0
#define CHAT_SETTING_BOOL 1
struct chat_setting_def {
const char* name;
int type;
int default_val;
int min_val;
int max_val;
};
/* ─── реестр всех известных настроек ─── */
static const struct chat_setting_def g_setting_defs[] = {
{"storage_autoload", CHAT_SETTING_BOOL, 1, 0, 1},
{"storage_autoload_maxsize_mb", CHAT_SETTING_INT, 10, 1, 500},
{"storage_maxsize_gb", CHAT_SETTING_INT, 1, 1, 100},
{"opus_codec_preset", CHAT_SETTING_INT, 1, 0, 4},
{"compressor_enabled", CHAT_SETTING_BOOL, 0, 0, 1},
{"compressor_max_gain_db", CHAT_SETTING_INT, 25, 1, 100},
{"compressor_rise_rate", CHAT_SETTING_INT, 10, 1, 100},
{"media_download_max_peers", CHAT_SETTING_INT, 3, 1, 16},
};
#define CHAT_SETTING_COUNT (sizeof(g_setting_defs) / sizeof(g_setting_defs[0]))
/* ─── состояние (текущие значения) ─── */
static int g_chat_values[CHAT_SETTING_COUNT];
static int g_chat_initialized = 0;
static void chat_setting_reset_defaults(void) {
for (size_t i = 0; i < CHAT_SETTING_COUNT; i++)
g_chat_values[i] = g_setting_defs[i].default_val;
g_chat_initialized = 1;
}
/* ─── поиск ─── */
static int chat_setting_find(const char* name) {
for (size_t i = 0; i < CHAT_SETTING_COUNT; i++)
if (strcmp(g_setting_defs[i].name, name) == 0) return (int)i;
return -1;
}
/* ─── apply: вызов специфичного для настройки кода ─── */
static void chat_setting_apply(void) {
/* будет заполняться по мере появления runtime-setter'ов в модулях:
if (idx == ...) some_module_set_xxx(value); */
}
/* ─── public API ─── */
int chat_setting_set(const char* name, const char* value) {
if (!name || !value || !*name || !*value) return -2;
if (!g_chat_initialized) chat_setting_reset_defaults();
int idx = chat_setting_find(name);
if (idx < 0) {
DEBUG_ERROR(CS_ID, "chat_setting: unknown setting '%s'", name);
return -1;
}
const struct chat_setting_def* def = &g_setting_defs[idx];
int v;
if (def->type == CHAT_SETTING_BOOL) {
if (strcmp(value, "0") == 0 || strcmp(value, "no") == 0 || strcmp(value, "false") == 0) v = 0;
else if (strcmp(value, "1") == 0 || strcmp(value, "yes") == 0 || strcmp(value, "true") == 0) v = 1;
else { DEBUG_ERROR(CS_ID, "chat_setting: '%s' invalid bool '%s' (expect 0/1)", name, value); return -2; }
} else {
char* end = NULL;
long lv = strtol(value, &end, 10);
if (!end || *end != '\0' || lv < def->min_val || lv > def->max_val) {
DEBUG_ERROR(CS_ID, "chat_setting: '%s' invalid int '%s' (range %d..%d)", name, value, def->min_val, def->max_val);
return -2;
}
v = (int)lv;
}
if (g_chat_values[idx] == v) return 0;
g_chat_values[idx] = v;
DEBUG_INFO(CS_ID, "chat_setting: %s = %s", name, value);
chat_setting_apply();
return 0;
}
const char* chat_setting_get(const char* name) {
if (!g_chat_initialized) chat_setting_reset_defaults();
int idx = chat_setting_find(name);
if (idx < 0) return NULL;
static char buf[16];
snprintf(buf, sizeof(buf), "%d", g_chat_values[idx]);
return buf;
}
int chat_setting_get_int(const char* name, int def) {
if (!g_chat_initialized) chat_setting_reset_defaults();
int idx = chat_setting_find(name);
return idx >= 0 ? g_chat_values[idx] : def;
}
+28
View File
@@ -0,0 +1,28 @@
/*
* chat_setting.h — chat-related settings (key-value API)
*
* Один API для всех потребителей: парсер конфига (headless) и GUI (через bridge).
* chat_setting_set(name, value) — валидация + хранение + apply к модулям.
* Возвращает 0=OK, -1=неизвестная настройка, -2=невалидное значение.
*/
#ifndef CHAT_SETTING_H
#define CHAT_SETTING_H
#ifdef __cplusplus
extern "C" {
#endif
/* Установить настройку. Применяет на лету если модуль уже инициализирован. */
int chat_setting_set(const char* name, const char* value);
/* Прочитать сохранённое значение. NULL если такой настройки нет. */
const char* chat_setting_get(const char* name);
/* Прочитать как int. Возвращает def если настройка не найдена. */
int chat_setting_get_int(const char* name, int def);
#ifdef __cplusplus
}
#endif
#endif /* CHAT_SETTING_H */
+9
View File
@@ -16,6 +16,7 @@
#include <netinet/in.h>
#endif
#include "../lib/mem.h"
#include "chat/chat_setting.h"
#define MAX_LINE_LEN 1024
#define INITIAL_ARRAY_CAPACITY 8
@@ -49,6 +50,7 @@ typedef enum {
SECTION_TCP_PROXY_SERVER,
SECTION_NETWORK,
SECTION_GUI,
SECTION_CHAT,
SECTION_NTP,
SECTION_LOG_UDP
} section_type_t;
@@ -755,6 +757,7 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
if (strcasecmp(section, "ntp") == 0) return SECTION_NTP;
if (strcasecmp(section, "gui") == 0) return SECTION_GUI;
if (strcasecmp(section, "chat") == 0) return SECTION_CHAT;
if (strcasecmp(section, "log_udp") == 0) return SECTION_LOG_UDP;
char *colon = strchr(section, ':');
@@ -985,6 +988,11 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'. Valid: enabled, server, interval", filename, line_num, key);
}
break;
case SECTION_CHAT:
if (chat_setting_set(key, value) < 0) {
/* chat_setting_set уже напечатал ошибку */
}
break;
case SECTION_GUI:
break;
default:
@@ -1108,6 +1116,7 @@ struct utun_config* parse_config_from_buf(const char *buf, size_t len, const cha
case SECTION_TCP_PROXY_SERVER: cfg->global.tcp_proxy_server_enabled = 1; if (strcmp(key, "tcp_recv_buf") == 0) cfg->global.tcp_recv_buf = atoi(value); break;
case SECTION_NETWORK: if (cur_network) parse_network(key, value, cur_network, filename, line_num); break;
case SECTION_NTP: if (strcmp(key, "enabled") == 0) cfg->global.ntp_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0; else if (strcmp(key, "server") == 0) { struct CFG_NTP_SERVER *ns = u_calloc(1, sizeof(struct CFG_NTP_SERVER)); if (ns) { strncpy(ns->name, value, sizeof(ns->name) - 1); ns->name[sizeof(ns->name) - 1] = '\0'; ns->next = cfg->global.ntp_servers; cfg->global.ntp_servers = ns; cfg->global.ntp_server_count++; } } else if (strcmp(key, "interval") == 0) { cfg->global.ntp_resync_interval = atoi(value); if (cfg->global.ntp_resync_interval < 60) cfg->global.ntp_resync_interval = 60; } else DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown ntp option '%s'", filename, line_num, key); break;
case SECTION_CHAT: chat_setting_set(key, value); break;
case SECTION_GUI: break;
case SECTION_LOG_UDP:
if (strcmp(key, "ip") == 0) strncpy(cfg->global.log_udp_ip, value, sizeof(cfg->global.log_udp_ip) - 1);