diff --git a/src/media_delivery/media_delivery.c b/src/media_delivery/media_delivery.c index d4dab73e..ac199fb6 100644 --- a/src/media_delivery/media_delivery.c +++ b/src/media_delivery/media_delivery.c @@ -1,6 +1,8 @@ #include "media_delivery.h" #include "media_delivery_proto.h" #include "media_download.h" +#include "media_index.h" +#include "../../lib/file_io.h" #include "../utun_instance.h" #include "../routing_layer/etcp_router.h" #include "../routing_layer/topo_group.h" @@ -994,21 +996,13 @@ static void md_handle_block_req(struct media_delivery_ctx* md, uint64_t from_nod int64_t block_length = block_end - block_start; /* find media_base */ - char media_base[512] = "/tmp/utun_media"; - { - sqlite3_stmt* ms = NULL; - sqlite3_prepare_v2(db, "SELECT value FROM ui_state WHERE key='media_base'", -1, &ms, NULL); - if (ms && sqlite3_step(ms) == SQLITE_ROW) { - const char* mb = (const char*)sqlite3_column_text(ms, 0); - if (mb) snprintf(media_base, sizeof(media_base), "%s", mb); - } - if (ms) sqlite3_finalize(ms); + char media_base[MEDIA_BASE_MAX], path[MEDIA_PATH_MAX]; + if (!loc || sqlite3_column_bytes(st, 0) != (int)strlen(loc) || media_index_get_base(db, media_base) + || media_index_path(media_base, loc, path)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "%s: invalid stored media path", MD_ID); + sqlite3_finalize(st); md->active_streams--; return; } - (void)media_base; - - char path[2048]; - snprintf(path, sizeof(path), "%s/%s", media_base, loc); - FILE* f = fopen(path, "rb"); + FILE* f = file_io_open_read(path); sqlite3_finalize(st); if (!f) { diff --git a/src/media_delivery/media_index.c b/src/media_delivery/media_index.c index b938b2ff..94f447be 100644 --- a/src/media_delivery/media_index.c +++ b/src/media_delivery/media_index.c @@ -216,43 +216,77 @@ int media_calc_block_size(uint64_t file_size) { return (int)target; } +/* Удаление regular/link без перехода через конечную ссылку; каталоги не удаляются. */ +static int mi_remove(const char* path) { + struct file_io_info info; + int found = file_io_inspect(path, 0, &info); + if (found == 1) return 0; + if (found || (info.type != FILE_IO_REGULAR && info.type != FILE_IO_LINK)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index cannot remove non-file path=%s", path); return -1; + } +#ifdef _WIN32 + wchar_t wide[FILE_IO_PATH_MAX]; + if (file_io_wide(path, wide)) return -1; + int rc = _wremove(wide); +#else + int rc = remove(path); +#endif + if (!rc || errno == ENOENT) return 0; + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index remove failed path=%s errno=%d: %s", path, errno, strerror(errno)); + return -1; +} + int media_index_delete_channel(sqlite3* db, const char* chat_id, const char* media_base) { - if (!db || !chat_id || !chat_id[0] || !media_base || !media_base[0]) return -1; - - char base[512]; - size_t base_len = strlen(media_base); - while (base_len > 0 && media_base[base_len - 1] == '/') base_len--; - snprintf(base, sizeof(base), "%.*s", (int)base_len, media_base); - - int files = 0, removed = 0; + if (!db || !chat_id || !*chat_id || !sqlite3_get_autocommit(db)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index delete invalid args/external transaction"); return -1; + } + 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; - if (sqlite3_prepare_v2(db, - "SELECT DISTINCT location FROM media_files WHERE chat_id=? AND location!=''", - -1, &st, NULL) == SQLITE_OK) { - sqlite3_bind_text(st, 1, chat_id, -1, SQLITE_STATIC); - while (sqlite3_step(st) == SQLITE_ROW) { - const char* loc = (const char*)sqlite3_column_text(st, 0); - if (!loc || !loc[0]) continue; - files++; - char full[1024]; - snprintf(full, sizeof(full), "%s/%s", base, loc); - if (remove(full) == 0) removed++; - else DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "media_index: delete_channel remove failed: %s", full); - } - sqlite3_finalize(st); + 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=?"; + 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; + /* Полный снимок и проверка всех путей до первого необратимого удаления. */ + while ((rc = sqlite3_step(st)) == SQLITE_ROW) { + const char* location = (const char*)sqlite3_column_text(st, 0); + char path[MEDIA_PATH_MAX]; + if (!location || sqlite3_column_bytes(st, 0) != (int)strlen(location) + || media_index_path(media_base, location, path) || count >= UINT32_MAX / sizeof(*files) - 1) goto failed; + struct mi_delete_file* grown = u_realloc(files, (count + 1) * sizeof(*files)); + if (!grown) goto failed; + files = grown; + snprintf(files[count].location, sizeof(files[count].location), "%s", location); + memcpy(files[count].path, path, strlen(path) + 1); + files[count].shared = sqlite3_column_int(st, 1); count++; } - - int rows = 0; - sqlite3_stmt* del = NULL; - if (sqlite3_prepare_v2(db, "DELETE FROM media_files WHERE chat_id=?", -1, &del, NULL) == SQLITE_OK) { - sqlite3_bind_text(del, 1, chat_id, -1, SQLITE_STATIC); - if (sqlite3_step(del) == SQLITE_DONE) rows = sqlite3_changes(db); - sqlite3_finalize(del); + 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); + 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; } + 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; } - - DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "media_index: delete_channel chat=%s files=%d removed=%d rows=%d", - chat_id, files, removed, rows); - return 0; + rc = sqlite3_finalize(st); st = NULL; + if (rc != SQLITE_OK) goto failed; + DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "media_index delete chat=%s paths=%u rows=%d failures=%d", chat_id, count, rows, errors); + u_free(files); return errors ? -1 : 0; +failed: + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index incomplete delete chat=%s rc=%d paths=%u: %s", + chat_id, rc, count, sqlite3_errmsg(db)); + if (st) sqlite3_finalize(st); + u_free(files); return -1; } int media_index_generate_uuid(uint8_t uuid_out[16]) {