You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
11 KiB
297 lines
11 KiB
// media_index.c — таблица media_files: init, commit, register_async |
|
#include "media_index.h" |
|
#include "../media_async/media_async.h" |
|
#include "../../lib/u_async.h" |
|
#include "../../lib/mem.h" |
|
#include "../../lib/debug_config.h" |
|
|
|
#include <openssl/evp.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <time.h> |
|
#include <unistd.h> |
|
|
|
#define MEDIA_BLOCK_MIN (10 * 1024 * 1024) |
|
#define MEDIA_BLOCK_MAX (25 * 1024 * 1024) |
|
#define MEDIA_BLOCK_TARGET 30 |
|
|
|
static int mi_insert(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, const uint8_t* node_sign, |
|
int64_t timestamp, int64_t file_size, |
|
int64_t chunk_size, int chunk, int64_t offset) { |
|
const char* sql = |
|
"INSERT INTO media_files(media_id,block_id,content_hash,chat_id,location," |
|
"node_id,node_sign,timestamp,file_size,chunk_size,chunk,offset)" |
|
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?)"; |
|
|
|
sqlite3_stmt* stmt = NULL; |
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: insert prep fail: %s", sqlite3_errmsg(db)); |
|
return -1; |
|
} |
|
sqlite3_bind_blob(stmt, 1, media_id, 16, SQLITE_STATIC); |
|
sqlite3_bind_blob(stmt, 2, block_id, 16, SQLITE_STATIC); |
|
sqlite3_bind_blob(stmt, 3, content_hash, 32, SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 4, chat_id, -1, SQLITE_STATIC); |
|
sqlite3_bind_text(stmt, 5, location, -1, SQLITE_STATIC); |
|
sqlite3_bind_int64(stmt, 6, (sqlite3_int64)node_id); |
|
sqlite3_bind_blob(stmt, 7, node_sign, 64, SQLITE_STATIC); |
|
sqlite3_bind_int64(stmt, 8, timestamp); |
|
sqlite3_bind_int64(stmt, 9, file_size); |
|
sqlite3_bind_int64(stmt, 10, chunk_size); |
|
sqlite3_bind_int(stmt, 11, chunk); |
|
sqlite3_bind_int64(stmt, 12, offset); |
|
|
|
int rc = sqlite3_step(stmt); |
|
sqlite3_finalize(stmt); |
|
|
|
if (rc != SQLITE_DONE) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: insert fail rc=%d: %s", rc, sqlite3_errmsg(db)); |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
int media_index_init(sqlite3* db) { |
|
if (!db) return -1; |
|
|
|
const char* sql = |
|
"CREATE TABLE IF NOT EXISTS media_files (" |
|
" id INTEGER PRIMARY KEY AUTOINCREMENT," |
|
" media_id BLOB NOT NULL," |
|
" block_id BLOB NOT NULL," |
|
" content_hash BLOB NOT NULL," |
|
" chat_id TEXT NOT NULL," |
|
" location TEXT NOT NULL," |
|
" node_id INTEGER NOT NULL," |
|
" node_sign BLOB NOT NULL," |
|
" timestamp INTEGER NOT NULL," |
|
" file_size INTEGER NOT NULL," |
|
" chunk_size INTEGER NOT NULL," |
|
" chunk INTEGER NOT NULL," |
|
" offset INTEGER NOT NULL);"; |
|
|
|
char* err = NULL; |
|
int rc = sqlite3_exec(db, sql, NULL, NULL, &err); |
|
if (rc != SQLITE_OK) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: CREATE TABLE failed: %s", err ? err : "?"); |
|
sqlite3_free(err); |
|
return -1; |
|
} |
|
|
|
sqlite3_exec(db, |
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_media_files_block_id ON media_files(block_id);", |
|
NULL, NULL, NULL); |
|
sqlite3_exec(db, |
|
"CREATE INDEX IF NOT EXISTS idx_media_files_media_id ON media_files(media_id);", |
|
NULL, NULL, NULL); |
|
sqlite3_exec(db, |
|
"CREATE INDEX IF NOT EXISTS idx_media_files_chat_id ON media_files(chat_id);", |
|
NULL, NULL, NULL); |
|
sqlite3_exec(db, |
|
"CREATE INDEX IF NOT EXISTS idx_media_files_node_id ON media_files(node_id);", |
|
NULL, NULL, NULL); |
|
sqlite3_exec(db, |
|
"CREATE INDEX IF NOT EXISTS idx_media_files_content_hash ON media_files(content_hash);", |
|
NULL, NULL, NULL); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "media_index: table+indices created"); |
|
return 0; |
|
} |
|
|
|
int media_calc_block_size(uint64_t file_size) { |
|
if (file_size == 0) return 0; |
|
uint64_t target = (file_size + MEDIA_BLOCK_TARGET - 1) / MEDIA_BLOCK_TARGET; |
|
if (target < MEDIA_BLOCK_MIN) target = MEDIA_BLOCK_MIN; |
|
if (target > MEDIA_BLOCK_MAX) target = MEDIA_BLOCK_MAX; |
|
return (int)target; |
|
} |
|
|
|
int media_index_generate_uuid(uint8_t uuid_out[16]) { |
|
ma_uuid(uuid_out); |
|
return 0; |
|
} |
|
|
|
void media_index_result_free(struct media_index_result* result) { |
|
if (!result) return; |
|
if (result->block_ids) { u_free(result->block_ids); result->block_ids = NULL; } |
|
if (result->block_sigs) { u_free(result->block_sigs); result->block_sigs = NULL; } |
|
} |
|
|
|
int media_index_commit(sqlite3* db, const struct media_index_result* result, |
|
uint64_t node_id, const uint8_t* ed25519_privkey, |
|
const char* chat_id, const char* dest_path, const char* media_base) { |
|
if (!db || !result || !chat_id || !dest_path || !media_base) return -1; |
|
|
|
int nb = result->num_blocks; |
|
int64_t bs = result->block_size; |
|
int64_t fs = result->file_size; |
|
int64_t ts = (int64_t)time(NULL); |
|
|
|
size_t base_len = strlen(media_base); |
|
if (base_len > 0 && media_base[base_len - 1] == '/') base_len--; |
|
const char* rel = dest_path; |
|
if (strncmp(rel, media_base, base_len) == 0 && rel[base_len] == '/') rel += base_len + 1; |
|
|
|
for (int n = 0; n < nb; n++) { |
|
int64_t cs = (n == nb - 1) ? fs - n * bs : bs; |
|
int64_t off = n * bs; |
|
|
|
char location[512]; |
|
snprintf(location, sizeof(location), "%s", rel); |
|
|
|
uint8_t sig_msg[92]; |
|
size_t soff = 0; |
|
memcpy(sig_msg + soff, result->media_id, 16); soff += 16; |
|
memcpy(sig_msg + soff, result->content_hash, 32); soff += 32; |
|
memcpy(sig_msg + soff, &ts, 8); soff += 8; |
|
memcpy(sig_msg + soff, &fs, 8); soff += 8; |
|
memcpy(sig_msg + soff, &cs, 8); soff += 8; |
|
int32_t cn = (int32_t)n; memcpy(sig_msg + soff, &cn, 4); soff += 4; |
|
memcpy(sig_msg + soff, &off, 8); soff += 8; |
|
memcpy(sig_msg + soff, &node_id, 8); soff += 8; |
|
|
|
uint8_t node_sign[64]; |
|
if (ma_sign_block(ed25519_privkey, sig_msg, soff, node_sign) != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: node_sign failed chunk=%d", n); |
|
return -1; |
|
} |
|
|
|
if (mi_insert(db, result->media_id, result->block_ids + n * 16, |
|
result->content_hash, chat_id, location, |
|
node_id, node_sign, ts, fs, cs, n, off) != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: insert failed chunk=%d", n); |
|
return -1; |
|
} |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "media_index: committed %d chunks for chat=%s path=%s", |
|
nb, chat_id, rel); |
|
return 0; |
|
} |
|
|
|
/* ─── register_async (worker + uasync callback) ─── */ |
|
|
|
struct mi_reg_ctx { |
|
struct UASYNC* ua; |
|
uint8_t ed25519_privkey[32]; |
|
char src_path[1024]; |
|
char dest_path[1024]; |
|
int copy_file; |
|
|
|
uint64_t node_id; |
|
char chat_id[64]; |
|
char media_base[512]; |
|
|
|
sqlite3* db; |
|
void (*cb)(void* arg, int err, const struct media_index_result* result); |
|
void* cb_arg; |
|
|
|
struct media_index_result result; |
|
int err; |
|
}; |
|
|
|
static void mi_reg_work(void* data) { |
|
struct mi_reg_ctx* ctx = (struct mi_reg_ctx*)data; |
|
|
|
if (ctx->copy_file && ma_copy_file(ctx->src_path, ctx->dest_path) != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: copy failed %s -> %s", ctx->src_path, ctx->dest_path); |
|
ctx->err = -10; return; |
|
} |
|
|
|
int64_t fsize = (int64_t)ma_file_size(ctx->dest_path); |
|
if (fsize <= 0) { ctx->err = -20; return; } |
|
ctx->result.file_size = fsize; |
|
|
|
int64_t bs = (int64_t)media_calc_block_size((uint64_t)fsize); |
|
int nb = (int)((fsize + bs - 1) / bs); |
|
ctx->result.block_size = bs; |
|
ctx->result.num_blocks = nb; |
|
|
|
if (ma_sha256_file(ctx->dest_path, ctx->result.content_hash) != 0) { |
|
ctx->err = -30; return; |
|
} |
|
|
|
ma_uuid(ctx->result.media_id); |
|
|
|
ctx->result.block_ids = u_malloc((size_t)nb * 16); |
|
ctx->result.block_sigs = u_malloc((size_t)nb * 64); |
|
if (!ctx->result.block_ids || !ctx->result.block_sigs) { ctx->err = -40; return; } |
|
|
|
FILE* f = fopen(ctx->dest_path, "rb"); |
|
if (!f) { ctx->err = -50; return; } |
|
|
|
for (int n = 0; n < nb; n++) { |
|
int64_t off = n * bs; |
|
int64_t sz = (n == nb - 1) ? fsize - off : bs; |
|
uint8_t* buf = u_malloc((size_t)sz); |
|
if (!buf) { fclose(f); ctx->err = -60; return; } |
|
fseeko(f, (off_t)off, SEEK_SET); |
|
size_t rd = fread(buf, 1, (size_t)sz, f); |
|
if (rd != (size_t)sz) { u_free(buf); fclose(f); ctx->err = -70; return; } |
|
|
|
ma_uuid(ctx->result.block_ids + n * 16); |
|
if (ma_sign_block(ctx->ed25519_privkey, buf, (size_t)sz, |
|
ctx->result.block_sigs + n * 64) != 0) { |
|
u_free(buf); fclose(f); ctx->err = -80; return; |
|
} |
|
u_free(buf); |
|
} |
|
fclose(f); |
|
ctx->err = 0; |
|
} |
|
|
|
static void mi_reg_done(void* arg, int err) { |
|
(void)err; |
|
struct mi_reg_ctx* ctx = (struct mi_reg_ctx*)arg; |
|
|
|
if (ctx->err == 0) { |
|
int rc = media_index_commit(ctx->db, &ctx->result, ctx->node_id, |
|
ctx->ed25519_privkey, ctx->chat_id, |
|
ctx->dest_path, ctx->media_base); |
|
if (rc != 0) ctx->err = -90; |
|
} |
|
|
|
if (ctx->cb) ctx->cb(ctx->cb_arg, ctx->err, ctx->err ? NULL : &ctx->result); |
|
|
|
media_index_result_free(&ctx->result); |
|
u_free(ctx); |
|
} |
|
|
|
void media_index_register_async( |
|
struct media_async* ma, struct UASYNC* ua, sqlite3* db, |
|
uint64_t node_id, const uint8_t* ed25519_privkey, |
|
const char* chat_id, |
|
const char* src_path, const char* dest_path, int copy_file, |
|
const char* media_base, |
|
void (*cb)(void* arg, int err, const struct media_index_result* result), |
|
void* cb_arg) { |
|
|
|
if (!ma || !ua || !db || !ed25519_privkey || !chat_id || !src_path || !dest_path || !media_base || !cb) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "media_index: register_async invalid args"); |
|
if (cb) cb(cb_arg, -1, NULL); |
|
return; |
|
} |
|
|
|
struct mi_reg_ctx* ctx = u_calloc(1, sizeof(*ctx)); |
|
if (!ctx) { cb(cb_arg, -1, NULL); return; } |
|
|
|
ctx->ua = ua; |
|
ctx->db = db; |
|
ctx->node_id = node_id; |
|
ctx->copy_file = copy_file; |
|
ctx->cb = cb; |
|
ctx->cb_arg = cb_arg; |
|
|
|
memcpy(ctx->ed25519_privkey, ed25519_privkey, 32); |
|
snprintf(ctx->src_path, sizeof(ctx->src_path), "%s", src_path); |
|
snprintf(ctx->dest_path, sizeof(ctx->dest_path), "%s", dest_path); |
|
snprintf(ctx->chat_id, sizeof(ctx->chat_id), "%s", chat_id); |
|
snprintf(ctx->media_base, sizeof(ctx->media_base), "%s", media_base); |
|
|
|
media_async_submit(ma, ua, mi_reg_work, ctx, mi_reg_done, ctx); |
|
}
|
|
|