From 3bb8f76b7d1c2bcf95862fda5483cc02b204ba93 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 15:03:41 +0200 Subject: [PATCH] Reuse the database media base when restarting chat --- src/chat/chat_core.c | 11 ++++++++- tests/test_media_index_regressions.c | 37 +++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/chat/chat_core.c b/src/chat/chat_core.c index 1dc1ed59..25705096 100644 --- a/src/chat/chat_core.c +++ b/src/chat/chat_core.c @@ -178,7 +178,16 @@ int chat_core_init(struct UTUN_INSTANCE* inst, const char* db_path) { const char* ls = strrchr(db_path, '/'); if (ls) snprintf(mbase, sizeof(mbase), "%.*s", ls == db_path ? 1 : (int)(ls - db_path), db_path); else snprintf(mbase, sizeof(mbase), "."); - if (media_index_set_base(cc->db, mbase)) { chat_core_destroy(inst); return -1; } + sqlite3_stmt* stored = NULL; + int rc = sqlite3_prepare_v2(cc->db, "SELECT value FROM ui_state WHERE key='media_base'", -1, &stored, NULL); + if (rc == SQLITE_OK) rc = sqlite3_step(stored); + int base_error = rc == SQLITE_ROW ? media_index_get_base(cc->db, mbase) + : rc == SQLITE_DONE ? media_index_set_base(cc->db, mbase) : -1; + int finish = stored ? sqlite3_finalize(stored) : SQLITE_OK; + if (base_error || finish != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "chat media_base initialization failed rc=%d: %s", rc, sqlite3_errmsg(cc->db)); + chat_core_destroy(inst); return -1; + } DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "%s: media_base=%s", CC_ID, mbase); } diff --git a/tests/test_media_index_regressions.c b/tests/test_media_index_regressions.c index d8902b14..f24c9be5 100644 --- a/tests/test_media_index_regressions.c +++ b/tests/test_media_index_regressions.c @@ -3,6 +3,7 @@ #include "../lib/file_io.h" #include "../lib/debug_config.h" #include "../src/media_delivery/media_index.h" +#include "../src/media_delivery/media_download.h" #include "../src/media_async/media_async.h" #include "../src/utun_instance.h" #include "../src/chat/chat_core.h" @@ -170,6 +171,7 @@ static void long_path(void) { static int deleted_events, failed_submissions, successful_submissions; static char delete_from_event[64]; +static void download_done(void* arg, int error) { assert(error); (*(int*)arg)++; } static void event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int length) { if (type == CHAT_EVT_CHANNEL_DELETED) deleted_events++; if (type == CHAT_EVT_MSG_SUBMITTED) { @@ -187,11 +189,25 @@ static void pending_deletion(void) { char config[1024]; snprintf(config, sizeof(config), "[global]\ndb_path=%s\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n", root); struct UASYNC* ua = uasync_create(); assert(ua); struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); assert(inst); - assert(!utun_core_start(inst) && !chat_service_start(inst)); chat_event_set_handler(inst, event); + assert(!utun_core_start(inst)); + struct denied denied = {.index = "idx_media_files_block_id"}; + assert(!sqlite3_set_authorizer(inst->topo_sqlite_db, authorizer, &denied)); + assert(chat_service_start(inst) == -1 && !inst->chat_started && !inst->chat_core); + assert(!sqlite3_set_authorizer(inst->topo_sqlite_db, NULL, NULL)); + assert(!chat_service_start(inst)); chat_event_set_handler(inst, event); chat_core_create_channel_auto(inst, "pending"); sqlite3_stmt* st = NULL; sqlite3* db = inst->topo_sqlite_db; - assert(!sqlite3_prepare_v2(db, "SELECT channel_id FROM channels WHERE name='pending'", -1, &st, NULL)); + assert(!sqlite3_prepare_v2(db, "SELECT channel_id,owner_node_id,x25519_pubkey,x25519_privkey,ed25519_pubkey," + "ed25519_privkey,signature FROM channels WHERE name='pending'", -1, &st, NULL)); assert(sqlite3_step(st) == SQLITE_ROW); char channel[64]; snprintf(channel, sizeof(channel), "%s", sqlite3_column_text(st, 0)); + struct chat_channel_create recreate = {.inst = inst}; + snprintf(recreate.channel_id, sizeof(recreate.channel_id), "%s", channel); + snprintf(recreate.name, sizeof(recreate.name), "pending"); recreate.owner_node_id = (uint64_t)sqlite3_column_int64(st, 1); + uint8_t* keys[] = {recreate.x25519_pubkey, recreate.x25519_privkey, recreate.ed25519_pubkey, recreate.ed25519_privkey, recreate.signature}; + for (int i = 0; i < 5; i++) { + int size = i == 4 ? 64 : 32; assert(sqlite3_column_bytes(st, i + 2) == size); + memcpy(keys[i], sqlite3_column_blob(st, i + 2), size); + } sqlite3_finalize(st); char source[MEDIA_PATH_MAX], destinations[2][MEDIA_PATH_MAX]; assert(!media_index_path(root, "source", source)); write_file(source); @@ -214,7 +230,8 @@ static void pending_deletion(void) { assert(!scalar(db, "SELECT count(*) FROM channels WHERE name='pending'")); assert(!scalar(db, "SELECT count(*) FROM media_files") && !scalar(db, "SELECT count(*) FROM media_preparations")); assert(ma_file_size(source) == 14 && ma_file_size(destinations[0]) == -1 && ma_file_size(destinations[1]) == -1); - chat_core_ensure_channel_ready(inst, channel); uasync_poll(ua, 0); + chat_core_create_channel(inst, &recreate); uasync_poll(ua, 0); + assert(scalar(db, "SELECT count(*) FROM channels WHERE name='pending'") == 1 && si_find(inst, channel)); assert(!scalar(db, "SELECT count(*) FROM media_files")); /* Обработчик MSG_SUBMITTED синхронный: удаление должно увидеть ещё живую задачу. */ @@ -228,6 +245,20 @@ static void pending_deletion(void) { assert(deleted_events == 2 && successful_submissions == 1 && failed_submissions == 3 && !CC(inst)->media_submissions); assert(!scalar(db, "SELECT count(*) FROM channels WHERE name='reentrant'")); assert(ma_file_size(destinations[1]) == -1 && ma_file_size(source) == 14); + + /* Отказ/синхронное завершение/дубликат имеют разные правила владения аргументом. */ + uint8_t signatures[128] = {0}; struct media_index_result download = result; download.block_sigs = signatures; + int completed = 0; + assert(!media_download_start(inst, 0, &download, destinations[0], root, 0, download_done, &completed, NULL, NULL)); + assert(completed == 1); /* Нет суперузлов: completion уже вызван, dl освобождён. */ + assert(!media_index_delete_channel(db, "0", root)); + completed = 0; + assert(!media_download_start(inst, 0, &download, destinations[0], root, inst->node_id ^ 0x55, + download_done, &completed, NULL, NULL)); + assert(!completed); + assert(media_download_start(inst, 0, &download, destinations[0], root, 0, download_done, &completed, NULL, NULL) == -1); + assert(!completed && !media_download_cancel(inst, download.media_id, NULL, 0) && completed == 1); + assert(!media_index_delete_channel(db, "0", root)); utun_instance_destroy(inst); uasync_destroy(ua, 0); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media regression pending deletion/two completions/recreation PASS"); }