350 lines
21 KiB
C
350 lines
21 KiB
C
/*
|
|
* chat_core — API каналов, сообщений, профиля и настроек одного узла.
|
|
* chat_service_start/stop управляют всем чат-сервисом поверх работающего ядра.
|
|
* Данные хранятся в общей SQLite; события для интерфейса доставляет chat_event.
|
|
* Порядок: utun_core_start → chat_service_start → команды чата → chat_service_stop.
|
|
* Сообщения отправляются через submit_message или submit_media_message; результат GUI — CHAT_EVT_MSG_SUBMITTED.
|
|
*
|
|
* Все DB-операции и сетевые функции выполняются в uasync-потоке.
|
|
* Все функции per-instance: первым аргументом struct UTUN_INSTANCE*.
|
|
* GUI вызывает через gui_bridge_post_uasync() trampoline-функции,
|
|
* чей arg-структура несёт inst.
|
|
*/
|
|
|
|
#ifndef CHAT_CORE_H
|
|
#define CHAT_CORE_H
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <sqlite3.h>
|
|
|
|
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; /* успешная отправка возвращает назначенное время */
|
|
uint8_t message_id[32]; /* успешная обычная отправка возвращает общий ID */
|
|
uint8_t reply_to[32]; /* общий ID оригинала в этом канале; нули = не ответ */
|
|
uint64_t request_id; /* ненулевой ID: trampoline/media отправляет CHAT_EVT_MSG_SUBMITTED */
|
|
};
|
|
|
|
// Подписать, сохранить и разослать сообщение. req/data остаются у вызывающего. 0/-1.
|
|
int chat_core_submit_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req);
|
|
|
|
/* Подготовить файл в worker, затем сохранить и разослать сообщение; результат — CHAT_EVT_MSG_SUBMITTED при request_id!=0.
|
|
* req и data копируются до возврата и остаются у вызывающего; файлы должны жить до завершения подготовки.
|
|
* media_copy копирует src в dest, media_video запускает транскодирование. Ошибка также приходит событием. */
|
|
void chat_core_submit_media_message(struct UTUN_INSTANCE* inst, struct chat_msg_submit* req);
|
|
|
|
// Ручная загрузка вложения; для зашифрованного файла также запрашивает ключ автора.
|
|
void chat_core_attachment_download(struct UTUN_INSTANCE* inst, const char* channel_id, int64_t msg_id);
|
|
|
|
// ── 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]; };
|
|
|
|
/* Подготовить таблицы, группу и синхронизацию; 0/-1. Ошибка удаляет новые runtime-ресурсы,
|
|
* проверенные данные БД сохраняются. Членства на других узлах не даёт. */
|
|
int 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-потоке. Другие узлы канал не теряют.
|
|
* Исходящие video/register задачи сначала завершаются; удаление продолжит последний completion.
|
|
* Новые исходящие медиа этого канала в это время отклоняются. Событие CHANNEL_DELETED приходит после удаления метаданных;
|
|
* ошибка файловой очистки логируется отдельно, записи неочищенных файлов сохраняются для повтора. */
|
|
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_<id>_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;
|
|
};
|
|
|
|
/* Передать в uasync блок chat_msg_submit, выделенный u_calloc/u_malloc; trampoline освобождает его.
|
|
* data отдельно не освобождается: удобно разместить содержимое в хвосте того же блока.
|
|
* Непустой media_src выбирает подготовку вложения, иначе — обычную отправку. Результат — по request_id. */
|
|
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);
|
|
|
|
// Лёгкий запрос нагрузки uasync для GUI. arg = inst, ответ CHAT_EVT_CPU_LOAD.
|
|
void chat_core_collect_cpu_load_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);
|
|
|
|
/* Диагностика карточки участника: запрос/codec находятся в chat_member.h. */
|
|
|
|
// ── 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/<sha256>.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);
|
|
|
|
/* Одно подписанное обновление нескольких собственных строковых опций. NULL value удаляет ключ.
|
|
* Остальные поля сохраняются; имя/аватар меняются своими API. Ошибка не меняет БД/Merkle.
|
|
* Вызов только в uasync-потоке, вне внешней SQLite-транзакции. */
|
|
struct chat_profile_option { const char* key; const char* value; };
|
|
int chat_core_set_my_profile_options(struct UTUN_INSTANCE* inst, const char* channel,
|
|
const struct chat_profile_option* options, size_t count);
|
|
|
|
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).
|
|
// uasync-поток; NULL/неинициализированный inst отклоняется с ERROR. Постановка в транспорт не подтверждает импорт.
|
|
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 */
|