Document preparation ownership and log exact media index failures
This commit is contained in:
1 parent
3bb8f76b7d
commit
f123a1bdeb
2 files changed
+33
-15
No files matched your search
@@ -67,7 +67,8 @@ static int mi_location_valid(const char* location) {
|
||||
|
||||
int media_index_path(const char* base, const char* location, char path[MEDIA_PATH_MAX]) {
|
||||
char clean[MEDIA_BASE_MAX];
|
||||
if (!path || mi_clean_path(base, clean, sizeof(clean)) || mi_location_valid(location)) return -1;
|
||||
if (!path) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index path without output"); return -1; }
|
||||
if (mi_clean_path(base, clean, sizeof(clean)) || mi_location_valid(location)) return -1;
|
||||
int size = snprintf(path, MEDIA_PATH_MAX, "%s%s%s", clean, clean[strlen(clean) - 1] == '/' ? "" : "/", location);
|
||||
if (size >= MEDIA_PATH_MAX || size < 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index joined path too long base=%s location=%s", clean, location); return -1;
|
||||
@@ -77,7 +78,8 @@ int media_index_path(const char* base, const char* location, char path[MEDIA_PAT
|
||||
|
||||
int media_index_location(const char* path, const char* base, char location[MEDIA_PATH_MAX]) {
|
||||
char full[MEDIA_PATH_MAX], root[MEDIA_BASE_MAX];
|
||||
if (!location || mi_clean_path(path, full, sizeof(full)) || mi_clean_path(base, root, sizeof(root))) return -1;
|
||||
if (!location) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index location without output"); return -1; }
|
||||
if (mi_clean_path(path, full, sizeof(full)) || mi_clean_path(base, root, sizeof(root))) return -1;
|
||||
size_t length = strlen(root);
|
||||
const char* relative = NULL;
|
||||
if (!strcmp(root, ".") && full[0] != '/' && !strchr(full, ':')) relative = full;
|
||||
@@ -113,16 +115,23 @@ int media_index_get_base(sqlite3* db, char base[MEDIA_BASE_MAX]) {
|
||||
|
||||
int media_index_set_base(sqlite3* db, const char* base) {
|
||||
char clean[MEDIA_BASE_MAX]; sqlite3_stmt* st = NULL;
|
||||
if (!db || mi_clean_path(base, clean, sizeof(clean))) return -1;
|
||||
if (!db) { DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index set base without DB"); return -1; }
|
||||
if (mi_clean_path(base, clean, sizeof(clean))) return -1;
|
||||
if (mi_exec(db, "CREATE TABLE IF NOT EXISTS ui_state(key TEXT PRIMARY KEY,value TEXT)")) return -1;
|
||||
int rc = sqlite3_prepare_v2(db, "INSERT INTO ui_state(key,value) VALUES('media_base',?) ON CONFLICT(key) DO NOTHING", -1, &st, NULL);
|
||||
if (rc == SQLITE_OK) rc = sqlite3_bind_text(st, 1, clean, -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 set base SQL failure requested=%s rc=%d: %s", clean, rc, sqlite3_errmsg(db));
|
||||
return -1;
|
||||
}
|
||||
char stored[MEDIA_BASE_MAX];
|
||||
if (rc == SQLITE_DONE && finish == SQLITE_OK && !media_index_get_base(db, stored) && !strcmp(stored, clean)) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index conflicting/invalid media_base requested=%s rc=%d: %s", clean, rc, sqlite3_errmsg(db));
|
||||
return -1;
|
||||
if (media_index_get_base(db, stored)) return -1;
|
||||
if (strcmp(stored, clean)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index base change rejected requested=%s stored=%s", clean, stored); return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int media_index_track_preparation(sqlite3* db, const char* chat_id, const char* path, const char* base) {
|
||||
@@ -182,8 +191,9 @@ static int mi_insert(sqlite3* db,
|
||||
int changed = sqlite3_changes(db), finish = sqlite3_finalize(stmt);
|
||||
|
||||
if (rc != SQLITE_DONE || finish != SQLITE_OK || changed != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index insert/conflict chat=%s chunk=%d rc=%d changed=%d: %s",
|
||||
chat_id, chunk, rc, changed, sqlite3_errmsg(db));
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index %s chat=%s block=%02x%02x%02x%02x chunk=%d path=%s rc=%d: %s",
|
||||
rc == SQLITE_DONE && finish == SQLITE_OK && !changed ? "conflicting block metadata" : "insert failed",
|
||||
chat_id, block_id[0], block_id[1], block_id[2], block_id[3], chunk, location, rc, sqlite3_errmsg(db));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
@@ -341,7 +351,9 @@ static int mi_result_valid(const struct media_index_result* result) {
|
||||
if (result && result->file_size > 0 && result->block_size > 0 && result->num_blocks > 0 && result->block_ids
|
||||
&& (uint64_t)result->num_blocks <= UINT32_MAX / 64
|
||||
&& (result->file_size - 1) / result->block_size + 1 == result->num_blocks) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index invalid block geometry/arrays"); return -1;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index invalid geometry bytes=%lld block_size=%lld count=%d ids=%p",
|
||||
(long long)(result ? result->file_size : 0), (long long)(result ? result->block_size : 0),
|
||||
result ? result->num_blocks : 0, (void*)(result ? result->block_ids : NULL)); return -1;
|
||||
}
|
||||
|
||||
/* Общий атомарный путь для автора и держателя скачанного файла. */
|
||||
@@ -369,7 +381,9 @@ static int mi_register_file(sqlite3* db, const struct media_index_result* result
|
||||
int32_t chunk = n; memcpy(message + used, &chunk, 4); used += 4;
|
||||
memcpy(message + used, &offset, 8); used += 8;
|
||||
memcpy(message + used, &node_id, 8); used += 8;
|
||||
if (ma_sign_block(key, message, used, signature)) { failed = 1; break; }
|
||||
if (ma_sign_block(key, message, used, signature)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index signing failed chat=%s chunk=%d", chat_id, n); failed = 1; break;
|
||||
}
|
||||
}
|
||||
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; }
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Порядок: init → register_async → callback с UUID, хешем и подписями блоков; затем можно анонсировать файл.
|
||||
* Register_async копирует/хеширует/подписывает файл в worker, затем commit и callback выполняются в uasync.
|
||||
* db/ma/ua заимствованы и должны жить до завершения; доступ к БД выполняется в потоке ядра.
|
||||
* Назначения учитываются в media_preparations до запуска worker; неуспешная подготовка оставляет их для удаления канала.
|
||||
* Перед delete_channel вызывающий должен дождаться всех своих workers/completions; функция их сама не отменяет.
|
||||
* Результат callback заимствован только до возврата. result_free освобождает block_ids/block_sigs, но не саму структуру. */
|
||||
#ifndef MEDIA_INDEX_H
|
||||
#define MEDIA_INDEX_H
|
||||
@@ -36,8 +38,8 @@ int media_index_get_base(sqlite3* db, char base[MEDIA_BASE_MAX]);
|
||||
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];
|
||||
uint8_t content_hash[32];
|
||||
uint8_t media_id[16]; // случайный идентификатор целого файла
|
||||
uint8_t content_hash[32]; // SHA256 полного содержимого
|
||||
int64_t file_size; // полный размер файла, байты
|
||||
int64_t block_size; // размер обычного блока, байты; последний может быть короче
|
||||
int num_blocks; // число блоков и элементов block_ids/block_sigs
|
||||
@@ -47,7 +49,7 @@ struct media_index_result {
|
||||
|
||||
// ── Инициализация таблицы ──
|
||||
|
||||
// Создать таблицу media_files и её индексы, сохранив существующие записи; 0/-1.
|
||||
// Создать media_files, media_preparations и индексы, сохранив записи; ошибка любого DDL даёт -1, успех 0.
|
||||
int media_index_init(sqlite3* db);
|
||||
|
||||
// ── Регистрация файлов и блоков ──
|
||||
@@ -63,8 +65,10 @@ int media_index_commit(
|
||||
uint64_t node_id, const uint8_t* ed25519_privkey,
|
||||
const char* chat_id, const char* dest_path, const char* media_base);
|
||||
|
||||
/* Зарегистрировать скачанный блок в media_files (location — путь к собранному файлу),
|
||||
* чтобы узел мог отдавать его как держатель. node_sign = нули (при отдаче не проверяется). */
|
||||
/* Зарегистрировать один скачанный блок: location — канонический путь относительно общей базы; 0/-1.
|
||||
* file_size/chunk_size >0, chunk/offset >=0, блок целиком помещается в файл.
|
||||
* node_sign новой записи — нули (при отдаче не проверяется); существующая подпись при совпадающем повторе сохраняется.
|
||||
* Для целого файла использовать register_downloaded_file; одиночные вызовы не образуют общую транзакцию. */
|
||||
int media_index_register_downloaded(sqlite3* db, const uint8_t* media_id, const uint8_t* block_id,
|
||||
const uint8_t* content_hash, const char* chat_id,
|
||||
const char* location, uint64_t node_id,
|
||||
|
||||
Reference in new issue
Block a user