/* * chat_core — API каналов, сообщений, профиля и настроек одного узла. * chat_service_start/stop управляют всем чат-сервисом поверх работающего ядра. * Данные хранятся в общей SQLite; события для интерфейса доставляет chat_event. * * Все DB-операции и сетевые функции выполняются в uasync-потоке. * Все функции per-instance: первым аргументом struct UTUN_INSTANCE*. * GUI вызывает через gui_bridge_post_uasync() trampoline-функции, * чей arg-структура несёт inst. */ #ifndef CHAT_CORE_H #define CHAT_CORE_H #include #include #include struct sqlite3; struct UTUN_INSTANCE; /* ── Жизненный цикл ── */ /* Запустить чат поверх core_start; UTUN не требуется. Повтор допустим; 0/-1. */ int chat_service_start(struct UTUN_INSTANCE* inst); /* Остановить чат, сохранив ядро, UTUN и БД. Повтор допустим; вызывать вне callbacks чата. * Отменяет доставку и ждёт native media workers; длительная задача может задержать stop. */ void chat_service_stop(struct UTUN_INSTANCE* inst); /* Внутренняя часть запуска: открыть/подключить БД и загрузить каналы. 0/-1. * Для запуска всего чат-сервиса использовать chat_service_start(). */ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path); /* Освободить chat_core после остановки остальных частей чата; общая БД остаётся у ядра. */ void chat_core_destroy(struct UTUN_INSTANCE* inst); /* Backfill: регистрирует уже скачанные медиафайлы (лежат на диске, но отсутствуют * в media_files) — вызывается после загрузки каналов/сообщений при старте. */ void chat_media_backfill(struct UTUN_INSTANCE* inst); /* Докачка недостающего медиа за последние storage_backfill_days дней. Проходит по * медиасообщениям каналов и запускает загрузку для тех, чей файл отсутствует на диске. */ void chat_media_autodownload_backfill(struct UTUN_INSTANCE* inst); /* Регистрирует триггер: после первого SYNC_DONE (+ дебаунс 2с) анонсирует локальные * блоки суперузлам и запускает chat_media_autodownload_backfill. Один раз за запуск чата. */ void chat_media_startup_backfill_init(struct UTUN_INSTANCE* inst); void chat_media_startup_backfill_destroy(struct UTUN_INSTANCE* inst); /* Заимствованная БД текущего chat_core; NULL до init/после destroy. */ struct sqlite3* chat_core_get_db(struct UTUN_INSTANCE* inst); /* 1 — chat_core инициализирован, 0 — нет; не проверяет доступность сети. */ int chat_core_is_initialized(struct UTUN_INSTANCE* inst); /* ── Отправка сообщения (GUI → uasync) ── */ struct chat_msg_submit { struct UTUN_INSTANCE* inst; char channel_id[64]; /* десятичный ID канала */ char content_type[32]; /* тип содержимого сообщения */ char media_src[1024]; /* исходный файл вложения */ char media_dest[1024]; /* путь подготовленного локального файла */ uint8_t media_copy; /* 1 = скопировать исходный файл при регистрации */ uint8_t media_video; /* 1 = подготовить как видео (пробинг+транскод) */ int duration_ms; /* видео: длительность (0 = неизвестно) */ int width; /* видео: ширина */ int height; /* видео: высота */ uint8_t* data; /* содержимое сообщения, заимствовано обычным submit */ uint32_t data_len; /* размер data в байтах */ uint64_t timestamp; /* входное время; отправка назначает новый timestamp через db_sync */ uint64_t reply_to_ts; /* 0 = не ответ */ uint64_t reply_to_node_id; /* node_id автора исходного сообщения */ }; /* Подписать, сохранить и разослать сообщение. req/data остаются у вызывающего. */ void chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req); /* Отправка медиа-сообщения (вложение): src->dest (copy) или видео (transcode). */ void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req); /* ── DB-операции (оставлены для интроспекции) ── */ /* Число локальных сообщений; 0 также при отсутствии БД или ошибке запроса. */ uint32_t chat_core_count(struct UTUN_INSTANCE* inst, const char* ch_id); /* Хеш записи по позиции с нуля в порядке (timestamp, node_id); hash_out — 32 байта. * 0 = запрос выполнен (нет записи — нулевой хеш), -1 = ошибка. */ int chat_core_chain_hash_at(struct UTUN_INSTANCE* inst, const char* ch_id, uint32_t pos, uint8_t* hash_out); /* Список ID: count:u16, затем [длина:u8, строка]; buf_size >= 2. 0/-1. * Списки ограничены буфером; out_len — записанные байты, числа в порядке байтов хоста. */ int chat_core_list_channels(struct UTUN_INSTANCE* inst, uint8_t* buf, size_t buf_size, size_t* out_len); /* Список участников: count:u16, затем node_id:u64; buf_size >= 2. 0/-1. */ int chat_core_list_peers(struct UTUN_INSTANCE* inst, const char* ch_id, uint8_t* buf, size_t buf_size, size_t* out_len); /* Ключи X25519/Ed25519 (по 32 байта) и список адресов из БД; buf_size >= 65. * out_len — размер результата; 0 = прочитано, -1 = нет узла или ошибка. */ int chat_core_load_nodeinfo(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t* buf, size_t buf_size, size_t* out_len); /* ── Создание канала (GUI → uasync) ── */ struct chat_channel_create { struct UTUN_INSTANCE* inst; char channel_id[64]; char name[128]; uint64_t owner_node_id; uint8_t x25519_pubkey[32]; uint8_t x25519_privkey[32]; uint8_t ed25519_pubkey[32]; uint8_t ed25519_privkey[32]; uint8_t signature[64]; }; struct chat_create_auto_arg { struct UTUN_INSTANCE* inst; char name[128]; }; /* Подготовить локальные таблицы, группу и синхронизацию канала. Членства на других узлах не даёт. */ void chat_core_ensure_channel_ready(struct UTUN_INSTANCE* inst, const char* ch_id); /* Создать канал по готовым ключам и метаданным req; req остаётся у вызывающего. */ void chat_core_create_channel(struct UTUN_INSTANCE* inst, struct chat_channel_create* req); void chat_core_create_channel_trampoline(void* arg); /* Сгенерировать ключи и создать канал, добавив себя владельцем. */ void chat_core_create_channel_auto(struct UTUN_INSTANCE* inst, const char* name); void chat_core_create_channel_auto_trampoline(void* arg); /* ── Локальное удаление канала (GUI → uasync) ── */ struct chat_delete_channel_req { struct UTUN_INSTANCE* inst; char ch_id[64]; }; /* Удалить канал с этого устройства: отмена медиа-загрузок, удаление группы * (соединения/синк), db_sync инстанса, таблиц msg_ и peers_, медиафайлов и * merkle-хешей. Работает в uasync-потоке. Другие узлы канал не теряют. */ void chat_core_delete_channel(struct UTUN_INSTANCE* inst, const char* ch_id); void chat_core_delete_channel_trampoline(void* arg); /* ── Утилиты ── */ /* Задать локальный ID в контексте чата; ключи и идентичность ядра не меняет. */ void chat_core_set_my_node_id(struct UTUN_INSTANCE* inst, uint64_t node_id); /* Сохранить имя узла и обновить собственные member-записи каналов. */ void chat_core_update_my_name(struct UTUN_INSTANCE* inst, const char* name); struct update_my_name_arg { struct UTUN_INSTANCE* inst; char name[128]; }; void chat_core_update_my_name_trampoline(void* arg); /* Обновить адреса topo_node и собственные member-записи каналов. */ void chat_core_sync_my_addresses(struct UTUN_INSTANCE* inst); /* Обновить своё userinfo во всех каналах: увеличить update_ts, подписать и запустить sync. * Адреса обновляются отдельно через 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); /* Per-channel «play sound on new message» (ui_state: ch__sound_on_message, default 1=on). * set — сохраняет и рассылает пирам через chat_sync (троттлинг db_sync на сервере). */ 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); struct chat_sound_arg { struct UTUN_INSTANCE* inst; char ch_id[64]; int on; }; void chat_core_set_sound_on_message_trampoline(void* arg); 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); struct chat_setting_arg { struct UTUN_INSTANCE* inst; char data[256]; }; void chat_core_set_setting_trampoline(void* arg); /* Запуск подключения к узлам выбранного канала (GUI → uasync) */ void chat_core_connect_channel(struct UTUN_INSTANCE* inst, const char* ch_id); void chat_core_connect_channel_trampoline(void* arg); /* Однократная попытка подключения к конкретному узлу канала (GUI → uasync) */ void chat_core_connect_node(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id); void chat_core_connect_node_trampoline(void* arg); struct chat_connect_node_req { struct UTUN_INSTANCE* inst; char ch_id[64]; uint64_t node_id; }; /* Трамплин для gui_bridge_post_uasync (GUI → uasync) */ void chat_core_submit_trampoline(void* arg); /* Трамплин для ручного запуска загрузки медиа (JNI → uasync) */ struct attachment_dl_req { struct UTUN_INSTANCE* inst; char channel_id[64]; int64_t msg_id; }; void chat_core_attachment_download_trampoline(void* arg); /* Обновление local_attrs для записи (вызывается из uasync-потока) */ int chat_core_update_local_attrs(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t ts, const uint8_t* author_sig, const char* local_attrs_json); /* Пометить голосовое сообщение как проигранное (добавляет "pl":1 в local_attrs) */ void chat_core_mark_voice_played(struct UTUN_INSTANCE* inst, const char* channel_id, uint64_t ts, uint64_t author_node_id); void chat_core_mark_voice_played_trampoline(void* arg); struct voice_played_req { struct UTUN_INSTANCE* inst; char channel_id[64]; uint64_t ts; uint64_t author_node_id; }; /* Сбор статуса (NTP + connections) и отправка в GUI. arg = struct UTUN_INSTANCE* (inst) */ void chat_core_collect_status_trampoline(void* arg); /* Сбор списка ETCP-соединений и отправка в GUI (binary packet). arg = inst */ void chat_core_collect_conn_list_trampoline(void* arg); struct chat_node_arg { struct UTUN_INSTANCE* inst; uint64_t node_id; }; /* Сбор полных метрик одного соединения (arg = u_malloc struct chat_node_arg) */ void chat_core_collect_conn_metrics_trampoline(void* arg); /* Сбор live-снапшота для панели деталей участника (arg = u_malloc struct chat_node_arg) */ void chat_core_collect_member_detail_trampoline(void* arg); /* ── Headless CLI API ── */ /* Список каналов с метаданными в JSON: [{id,name,owner_id,owner_name,peers,msgs,online}] */ int chat_core_get_channels_json(struct UTUN_INSTANCE* inst, char* buf, size_t buf_size, size_t* out_len); /* Сообщения канала в JSON: [{id,ts,author_id,author_name,content_type,data,local_attrs}]. * От новых к старым; count<=0 — до 100 записей, offset — сколько пропустить. */ int chat_core_get_messages_json(struct UTUN_INSTANCE* inst, const char* ch_id, int count, int offset, char* buf, size_t buf_size, size_t* out_len); /* Мемберы в JSON: [{node_id,name,online,connected,adm_tags,x25519,ed25519,addrs}]. * connected означает наличие транспорта в реестре, а не его UP или готовность группы. */ int chat_core_get_members_json(struct UTUN_INSTANCE* inst, const char* ch_id, char* buf, size_t buf_size, size_t* out_len); /* Имя узла из таблицы nodes */ int chat_core_get_node_name(struct UTUN_INSTANCE* inst, uint64_t node_id, char* out, size_t sz); /* Ссылка на описание avatars/.json внутри дерева своего UID/канала. server=0 удаляет ссылку. */ int chat_core_set_my_avatar(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t server, const char* file); int chat_core_name_userinfo(const char* name, char* out, size_t capacity); int chat_core_get_my_profile(struct UTUN_INSTANCE* inst, const char* channel, char* out, size_t capacity, uint8_t signature[64], uint64_t* timestamp); struct chat_avatar_req { struct UTUN_INSTANCE* inst; uint64_t id, group, server; char file[70]; }; void chat_core_set_my_avatar_trampoline(void* arg); /* ── Редактирование adm_tags (владелец канала) ── */ #define CHAT_MEMBER_TAGS_MAX_KEYS 16 #define CHAT_MEMBER_TAGS_KEY_SZ 64 struct chat_member_tags; /* Загрузить текущие adm_tags мембера в память. Возвращает NULL если мембера нет. */ struct chat_member_tags* chat_member_tags_load(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id); /* Установить ключ. val=NULL → удалить ключ. ver — особый, автоинкремент при commit. */ int chat_member_tags_set(struct chat_member_tags* t, const char* key, const char* val); /* Получить значение ключа. Возвращает NULL если ключ не найден. */ const char* chat_member_tags_get(struct chat_member_tags* t, const char* key); /* Заменить все теги данными из JSON-строки (плоский, строковые значения). */ int chat_member_tags_replace(struct chat_member_tags* t, const char* json); /* Сохранить: сериализовать в JSON, инкрементировать ver, подписать канальным Ed25519, * записать через member_sync_put() → синхронизируется всем. */ int chat_member_tags_commit(struct UTUN_INSTANCE* inst, struct chat_member_tags* t); /* Освободить без сохранения. */ void chat_member_tags_free(struct chat_member_tags* t); /* Трамплин для gui_bridge: arg = struct chat_member_tags_req* (доверенная память). */ struct chat_member_tags_req { struct UTUN_INSTANCE* inst; char ch_id[64]; uint64_t node_id; char json[512]; }; void chat_member_tags_commit_trampoline(void* arg); /* ── Передача прав админа (владелец → другому узлу) ── */ /* Передать оба канальных privkey выбранному узлу (encrypted+signed через etcp_router). */ void chat_core_transfer_admin(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t target_node_id); void chat_core_transfer_admin_trampoline(void* arg); struct chat_transfer_admin_req { struct UTUN_INSTANCE* inst; char ch_id[64]; uint64_t target_node_id; }; #endif /* CHAT_CORE_H */