From cf12ee03fab3d77628c8487ae5c4dec3895a6d24 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 12:52:55 +0100 Subject: [PATCH] Add per-device file facts and frozen storage policy to the schema --- src/dm/dm_schema.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++ src/dm/dm_schema.h | 3 +++ 2 files changed, 53 insertions(+) diff --git a/src/dm/dm_schema.c b/src/dm/dm_schema.c index fc96f7767..ea5123af7 100644 --- a/src/dm/dm_schema.c +++ b/src/dm/dm_schema.c @@ -26,6 +26,56 @@ static int schema_has_column(sqlite3* db, const char* table, const char* column) return (rc == SQLITE_ROW || rc == SQLITE_DONE) && final == SQLITE_OK ? found : schema_error(db, "column lookup"); } +int dm_media_schema_init(struct UTUN_INSTANCE* inst) { + sqlite3* db = inst ? inst->topo_sqlite_db : NULL; + if (!db) return schema_error(db, "media database absent"); + if (schema_exec(db, "SAVEPOINT dm_media_schema")) return -1; + const char* files = "CREATE TABLE IF NOT EXISTS dm_files(id BLOB PRIMARY KEY,conv_id TEXT NOT NULL,dir INTEGER NOT NULL," + "seq INTEGER NOT NULL,author INTEGER NOT NULL,recipient INTEGER NOT NULL,plain_size INTEGER NOT NULL," + "cipher_hash BLOB NOT NULL,body BLOB NOT NULL,group_id INTEGER NOT NULL,local_path TEXT NOT NULL," + "ready INTEGER NOT NULL DEFAULT 0,holder INTEGER NOT NULL,expires INTEGER NOT NULL DEFAULT 0,receipt BLOB," + "downloading INTEGER NOT NULL DEFAULT 0,retry_at INTEGER NOT NULL DEFAULT 0,manual INTEGER NOT NULL DEFAULT 0," + "first_attempt INTEGER NOT NULL DEFAULT 0,UNIQUE(conv_id,author,seq))"; + if (schema_exec(db, files)) goto fail; + int found = schema_has_column(db, "dm_files", "manual"); + if (found < 0) goto fail; + if (!found) { + if (schema_exec(db, "ALTER TABLE dm_files RENAME TO dm_files_previous") || schema_exec(db, files) || + schema_exec(db, "INSERT INTO dm_files(id,conv_id,dir,seq,author,recipient,plain_size,cipher_hash,body,group_id," + "local_path,ready,holder,expires,receipt,downloading,retry_at) SELECT id,conv_id,dir,seq,author,recipient," + "plain_size,cipher_hash,body,group_id,local_path,ready,holder,expires,receipt,downloading,retry_at FROM dm_files_previous") || + schema_exec(db, "DROP TABLE dm_files_previous")) goto fail; + } + if (schema_exec(db, "CREATE TABLE IF NOT EXISTS dm_file_targets(id BLOB NOT NULL,node INTEGER NOT NULL,side INTEGER NOT NULL," + "received BLOB,taken BLOB,PRIMARY KEY(id,node));" + "CREATE TABLE IF NOT EXISTS dm_file_sources(id BLOB NOT NULL,node INTEGER NOT NULL,group_id INTEGER NOT NULL," + "expires INTEGER NOT NULL DEFAULT 0,PRIMARY KEY(id,node));" + "CREATE TABLE IF NOT EXISTS dm_custody(id BLOB PRIMARY KEY,author INTEGER NOT NULL,recipient INTEGER NOT NULL," + "plain_size INTEGER NOT NULL,cipher_size INTEGER NOT NULL,body BLOB NOT NULL,body_hash BLOB NOT NULL," + "group_id INTEGER NOT NULL,supernode INTEGER NOT NULL,expires INTEGER NOT NULL,ready INTEGER NOT NULL DEFAULT 0," + "receipt BLOB,retry_at INTEGER NOT NULL DEFAULT 0);" + "CREATE TABLE IF NOT EXISTS dm_custody_done(id BLOB PRIMARY KEY,body_hash BLOB NOT NULL,receipt BLOB);" + "CREATE TABLE IF NOT EXISTS dm_media_jobs(id BLOB PRIMARY KEY,author INTEGER NOT NULL,recipient INTEGER NOT NULL," + "seq INTEGER NOT NULL,body BLOB,body_hash BLOB NOT NULL,group_id INTEGER NOT NULL,holder INTEGER NOT NULL DEFAULT 0," + "stored INTEGER NOT NULL DEFAULT 0,expires INTEGER NOT NULL DEFAULT 0,expired INTEGER NOT NULL DEFAULT 0," + "receipt BLOB,retry_at INTEGER NOT NULL DEFAULT 0)")) goto fail; + const char* tables[] = {"dm_custody", "dm_custody_done", "dm_media_jobs"}; + for (unsigned i = 0; i < 3; i++) { + found = schema_has_column(db, tables[i], "delete_on_open"); + if (found < 0) goto fail; + if (!found) { + char sql[256]; + snprintf(sql, sizeof(sql), "ALTER TABLE %s ADD COLUMN delete_on_open INTEGER NOT NULL DEFAULT %d", tables[i], i == 2 ? -1 : 1); + if (schema_exec(db, sql)) goto fail; + } + } + if (schema_exec(db, "RELEASE dm_media_schema")) goto fail; + DEBUG_INFO(DEBUG_CATEGORY_DM, "DM media schema: author identity, device receipts and retention policy ready"); + return 0; +fail: + schema_exec(db, "ROLLBACK TO dm_media_schema"); schema_exec(db, "RELEASE dm_media_schema"); return -1; +} + int dm_schema_init(struct UTUN_INSTANCE* inst) { sqlite3* db = inst ? inst->topo_sqlite_db : NULL; if (!db) return schema_error(db, "missing database"); diff --git a/src/dm/dm_schema.h b/src/dm/dm_schema.h index 214a24955..1089c5384 100644 --- a/src/dm/dm_schema.h +++ b/src/dm/dm_schema.h @@ -8,6 +8,9 @@ struct UTUN_INSTANCE; /* 0/-1: создать схему и перенести существующие DEVICE-записи без переподписи. */ int dm_schema_init(struct UTUN_INSTANCE* inst); +/* Общая схема файлов endpoint/S/M и квитанций устройств; вызывается до media owners. + * Сохранённые DEVICE-файлы остаются, уникальность меняется с dir на author. 0/-1. */ +int dm_media_schema_init(struct UTUN_INSTANCE* inst); /* 0/-1: прочитать следующий seq автора. Резервирование выполняет take в той же * транзакции, что запись сообщения; до commit тело нельзя передавать в сеть. */ int dm_sequence_next(struct UTUN_INSTANCE* inst, uint64_t* seq);