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

// 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);
}