From b3a223521c8fc545fe1f7d94d61a55d72583dcad Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 14:44:24 +0200 Subject: [PATCH] Track unfinished media destinations for channel cleanup --- src/media_delivery/media_index.c | 51 ++++++++++++++++++++++++++++---- src/media_delivery/media_index.h | 3 ++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/media_delivery/media_index.c b/src/media_delivery/media_index.c index 94f447be..a10d01f3 100644 --- a/src/media_delivery/media_index.c +++ b/src/media_delivery/media_index.c @@ -124,6 +124,23 @@ int media_index_set_base(sqlite3* db, const char* base) { return -1; } +int media_index_track_preparation(sqlite3* db, const char* chat_id, const char* path, const char* base) { + char location[MEDIA_PATH_MAX]; sqlite3_stmt* st = NULL; + if (!db || !chat_id || !*chat_id || strlen(chat_id) >= 64 || media_index_location(path, base, location)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index invalid preparation path/channel"); return -1; + } + int rc = sqlite3_prepare_v2(db, "INSERT INTO media_preparations(chat_id,location) VALUES(?,?) ON CONFLICT DO NOTHING", + -1, &st, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, chat_id, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 2, location, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(st); + int finish = st ? sqlite3_finalize(st) : SQLITE_OK; + if (rc == SQLITE_DONE && finish == SQLITE_OK) { + DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "media_index preparation tracked chat=%s path=%s", chat_id, location); return 0; + } + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index track preparation chat=%s rc=%d: %s", chat_id, rc, sqlite3_errmsg(db)); return -1; +} + static int mi_insert(sqlite3* db, const uint8_t* media_id, const uint8_t* block_id, const uint8_t* content_hash, @@ -198,7 +215,9 @@ int media_index_init(sqlite3* db) { return -1; } - if (mi_exec(db, "CREATE UNIQUE INDEX IF NOT EXISTS idx_media_files_block_id ON media_files(block_id)") + if (mi_exec(db, "CREATE TABLE IF NOT EXISTS media_preparations(chat_id TEXT NOT NULL,location TEXT NOT NULL," + "PRIMARY KEY(chat_id,location))") + || mi_exec(db, "CREATE UNIQUE INDEX IF NOT EXISTS idx_media_files_block_id ON media_files(block_id)") || mi_exec(db, "CREATE INDEX IF NOT EXISTS idx_media_files_media_id ON media_files(media_id)") || mi_exec(db, "CREATE INDEX IF NOT EXISTS idx_media_files_chat_id ON media_files(chat_id)") || mi_exec(db, "CREATE INDEX IF NOT EXISTS idx_media_files_node_id ON media_files(node_id)") @@ -243,9 +262,9 @@ int media_index_delete_channel(sqlite3* db, const char* chat_id, const char* med struct mi_delete_file { char location[MEDIA_PATH_MAX]; char path[MEDIA_PATH_MAX]; int shared; }; struct mi_delete_file* files = NULL; uint32_t count = 0; sqlite3_stmt* st = NULL; - const char* query = "SELECT DISTINCT location,EXISTS(SELECT 1 FROM media_files other" - " WHERE other.location=mine.location AND other.chat_id!=mine.chat_id)" - " FROM media_files mine WHERE chat_id=?"; + const char* query = "WITH paths AS (SELECT chat_id,location FROM media_files UNION SELECT chat_id,location FROM media_preparations)" + " SELECT location,EXISTS(SELECT 1 FROM paths other WHERE other.location=mine.location AND other.chat_id!=mine.chat_id)" + " FROM paths mine WHERE chat_id=?"; int rc = sqlite3_prepare_v2(db, query, -1, &st, NULL); if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, chat_id, -1, SQLITE_STATIC); if (rc != SQLITE_OK) goto failed; @@ -265,16 +284,23 @@ int media_index_delete_channel(sqlite3* db, const char* chat_id, const char* med if (rc != SQLITE_DONE) goto failed; rc = sqlite3_finalize(st); st = NULL; if (rc != SQLITE_OK) goto failed; - rc = sqlite3_prepare_v2(db, "DELETE FROM media_files WHERE chat_id=? AND location=?", -1, &st, NULL); + rc = sqlite3_prepare_v2(db, "DELETE FROM media_preparations WHERE chat_id=? AND location=?", -1, &st, NULL); if (rc != SQLITE_OK) goto failed; int errors = 0, rows = 0; for (uint32_t i = 0; i < count; i++) { if (!files[i].shared && mi_remove(files[i].path)) { errors++; continue; } + sqlite3_stmt* blocks = NULL; + rc = sqlite3_prepare_v2(db, "DELETE FROM media_files WHERE chat_id=? AND location=?", -1, &blocks, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(blocks, 1, chat_id, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(blocks, 2, files[i].location, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(blocks); + int finish = blocks ? sqlite3_finalize(blocks) : SQLITE_OK; + if (rc != SQLITE_DONE || finish != SQLITE_OK) goto failed; + rows += sqlite3_changes(db); rc = sqlite3_bind_text(st, 1, chat_id, -1, SQLITE_STATIC); if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 2, files[i].location, -1, SQLITE_STATIC); if (rc == SQLITE_OK) rc = sqlite3_step(st); if (rc != SQLITE_DONE) goto failed; - rows += sqlite3_changes(db); rc = sqlite3_reset(st); if (rc != SQLITE_OK) goto failed; } @@ -346,6 +372,18 @@ static int mi_register_file(sqlite3* db, const struct media_index_result* result if (mi_insert(db, result->media_id, result->block_ids + (size_t)n * 16, result->content_hash, chat_id, location, node_id, signature, ts, result->file_size, size, n, offset)) { failed = 1; break; } } + if (!failed) { + sqlite3_stmt* st = NULL; + int rc = sqlite3_prepare_v2(db, "DELETE FROM media_preparations WHERE chat_id=? AND location=?", -1, &st, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, chat_id, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 2, location, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(st); + int finish = st ? sqlite3_finalize(st) : SQLITE_OK; + if (rc != SQLITE_DONE || finish != SQLITE_OK) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index clearing preparation failed chat=%s rc=%d: %s", chat_id, rc, sqlite3_errmsg(db)); + failed = 1; + } + } if (mi_end_registration(db, failed)) return -1; DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "media_index registered chat=%s blocks=%d path=%s signed=%d", chat_id, result->num_blocks, location, key != NULL); @@ -505,6 +543,7 @@ void media_index_register_async( || strlen(media_base) >= sizeof(((struct mi_reg_ctx*)0)->media_base)) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index register oversized/empty path"); cb(cb_arg, -1, NULL); return; } + if (media_index_track_preparation(db, chat_id, dest_path, media_base)) { cb(cb_arg, -1, NULL); return; } struct mi_reg_ctx* ctx = u_calloc(1, sizeof(*ctx)); if (!ctx) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index context allocation failed"); cb(cb_arg, -1, NULL); return; } diff --git a/src/media_delivery/media_index.h b/src/media_delivery/media_index.h index 65c650c2..5722811a 100644 --- a/src/media_delivery/media_index.h +++ b/src/media_delivery/media_index.h @@ -31,6 +31,9 @@ int media_index_path(const char* base, const char* location, char path[MEDIA_PAT * get возвращает проверенную базу; 0/-1. Смена базы при работающем ядре не поддерживается. */ int media_index_set_base(sqlite3* db, const char* base); int media_index_get_base(sqlite3* db, char base[MEDIA_BASE_MAX]); +/* Учесть назначение до файловой подготовки. Ошибка подготовки оставляет запись для delete_channel; + * успешная регистрация файла удаляет её в той же транзакции. Сам файл здесь не меняется. 0/-1. */ +int media_index_track_preparation(sqlite3* db, const char* chat_id, const char* path, const char* base); struct media_index_result { uint8_t media_id[16];