forked from dlang/cdcdb
249 lines
7.5 KiB
D
249 lines
7.5 KiB
D
module cdcdb.storage;
|
||
|
||
import cdcdb.dblite;
|
||
import cdcdb.core;
|
||
import cdcdb.snapshot;
|
||
|
||
import zstd : compress, Level;
|
||
|
||
/// Контекст создания снимка (идентификаторы и процесс).
|
||
struct Context
|
||
{
|
||
long uid; /// UID процесса (effective).
|
||
long ruid; /// Real UID процесса.
|
||
string uidName; /// Имя пользователя для UID.
|
||
string ruidName; /// Имя пользователя для RUID.
|
||
string process; /// Имя процесса.
|
||
}
|
||
|
||
/**
|
||
* Высокоуровневый фасад для хранения: разбивает данные на чанки CDC,
|
||
* сохраняет чанки/блобы в SQLite через `DBLite`, связывает их в снимки
|
||
* и возвращает объекты `Snapshot` для последующего чтения и удаления.
|
||
*
|
||
* Возможности:
|
||
* - Разбиение FastCDC (контентно-зависимое, настраиваемые размеры/маски).
|
||
* - Опциональное сжатие Zstandard (уровень задаётся).
|
||
* - Идемпотентное создание снимков: пропускает, если последний снимок совпадает.
|
||
*
|
||
* Типичное использование:
|
||
* ---
|
||
* auto store = new Storage("base.db", true, Level.max);
|
||
* store.setupCDC(4096, 8192, 16384, 0x3FFF, 0x03FF);
|
||
* Context ctx;
|
||
* auto snap = store.newSnapshot("my.txt", data, ctx, "первичный импорт");
|
||
* auto bytes = snap.data(); // восстановить содержимое
|
||
*
|
||
* auto removed = store.removeSnapshots("my.txt"); // удалить по имени файла
|
||
* ---
|
||
*/
|
||
final class Storage
|
||
{
|
||
private:
|
||
// Параметры БД
|
||
DBLite _db;
|
||
bool _zstd;
|
||
int _level;
|
||
// Настройки CDC
|
||
CDC _cdc;
|
||
size_t _minSize;
|
||
size_t _normalSize;
|
||
size_t _maxSize;
|
||
size_t _maskS;
|
||
size_t _maskL;
|
||
|
||
/// Инициализация параметров FastCDC.
|
||
void initCDC(size_t minSize = 256, size_t normalSize = 512, size_t maxSize = 1024,
|
||
size_t maskS = 0xFF, size_t maskL = 0x0F)
|
||
{
|
||
_minSize = minSize;
|
||
_normalSize = normalSize;
|
||
_maxSize = maxSize;
|
||
_maskS = maskS;
|
||
_maskL = maskL;
|
||
// CDC не хранит динамического состояния, переинициализация безопасна
|
||
_cdc = new CDC(_minSize, _normalSize, _maxSize, _maskS, _maskL);
|
||
}
|
||
|
||
public:
|
||
/// Конструктор: открывает/создаёт БД и подготавливает фасад.
|
||
///
|
||
/// Параметры:
|
||
/// database = путь к файлу SQLite
|
||
/// zstd = включить Zstd-сжатие для блобов
|
||
/// level = уровень сжатия (см. `zstd.Level`)
|
||
/// busyTimeout = таймаут ожидания блокировки SQLite (мс)
|
||
/// maxRetries = число повторов при SQLITE_BUSY/LOCKED
|
||
this(string database, bool zstd = false, int level = Level.base,
|
||
size_t busyTimeout = 3000, size_t maxRetries = 3)
|
||
{
|
||
_db = new DBLite(database, busyTimeout, maxRetries);
|
||
_zstd = zstd;
|
||
_level = level;
|
||
initCDC();
|
||
}
|
||
|
||
/// Перенастроить параметры CDC (влияет на будущие снимки).
|
||
void setupCDC(size_t minSize, size_t normalSize, size_t maxSize,
|
||
size_t maskS, size_t maskL)
|
||
{
|
||
initCDC(minSize, normalSize, maxSize, maskS, maskL);
|
||
}
|
||
|
||
/// Создаёт новый снимок из массива байт.
|
||
///
|
||
/// - Разбивает данные по текущим параметрам FastCDC.
|
||
/// - Опционально сжимает чанки Zstd.
|
||
/// - Сохраняет уникальные блобы и связывает их со снимком.
|
||
/// - Если последний снимок для файла совпадает по SHA-256, возвращает `null`.
|
||
///
|
||
/// Параметры:
|
||
/// file = имя файла (метка снимка)
|
||
/// data = содержимое файла
|
||
/// context = контекст (uid, ruid, процесс и т.д.)
|
||
/// description = необязательное описание
|
||
///
|
||
/// Возвращает: объект `Snapshot` или `null`
|
||
///
|
||
/// Исключения: при пустых данных или ошибках базы
|
||
Snapshot newSnapshot(string file, const(ubyte)[] data, Context context,
|
||
string description = string.init)
|
||
{
|
||
if (data.length == 0)
|
||
{
|
||
throw new Exception("Данные имеют нулевую длину");
|
||
}
|
||
|
||
import std.digest.sha : SHA256, digest;
|
||
|
||
ubyte[32] sha256 = digest!SHA256(data);
|
||
|
||
// Если последний снимок совпадает — пропустить
|
||
if (_db.isLast(file, sha256))
|
||
return null;
|
||
|
||
_db.beginImmediate();
|
||
bool ok;
|
||
|
||
scope (exit)
|
||
{
|
||
if (!ok)
|
||
_db.rollback();
|
||
}
|
||
scope (success)
|
||
{
|
||
_db.commit();
|
||
}
|
||
|
||
// Запись пользователей/файлов/процессов
|
||
_db.addUser(context.uid, context.uidName);
|
||
if (context.uid != context.ruid)
|
||
{
|
||
_db.addUser(context.ruid, context.ruidName);
|
||
}
|
||
_db.addFile(file);
|
||
_db.addProcess(context.process);
|
||
|
||
// Метаданные снимка
|
||
DBSnapshot dbSnapshot;
|
||
dbSnapshot.file = file;
|
||
dbSnapshot.sha256 = sha256;
|
||
dbSnapshot.description = description;
|
||
dbSnapshot.sourceLength = data.length;
|
||
dbSnapshot.algoMin = _minSize;
|
||
dbSnapshot.algoNormal = _normalSize;
|
||
dbSnapshot.algoMax = _maxSize;
|
||
dbSnapshot.maskS = _maskS;
|
||
dbSnapshot.maskL = _maskL;
|
||
dbSnapshot.uid = context.uid;
|
||
dbSnapshot.ruid = context.ruid;
|
||
dbSnapshot.process = context.process;
|
||
|
||
auto idSnapshot = _db.addSnapshot(dbSnapshot);
|
||
|
||
// Чанки и блобы
|
||
DBSnapshotChunk dbSnapshotChunk;
|
||
DBBlob dbBlob;
|
||
dbBlob.zstd = _zstd;
|
||
|
||
Chunk[] chunks = _cdc.split(data);
|
||
|
||
foreach (chunk; chunks)
|
||
{
|
||
dbBlob.sha256 = chunk.sha256;
|
||
dbBlob.size = chunk.size;
|
||
|
||
auto content = data[chunk.offset .. chunk.offset + chunk.size];
|
||
|
||
if (_zstd)
|
||
{
|
||
ubyte[] zBytes = compress(content, _level);
|
||
size_t zSize = zBytes.length;
|
||
ubyte[32] zHash = digest!SHA256(zBytes);
|
||
|
||
dbBlob.zSize = zSize;
|
||
dbBlob.zSha256 = zHash;
|
||
dbBlob.content = zBytes;
|
||
}
|
||
else
|
||
{
|
||
dbBlob.content = content.dup;
|
||
}
|
||
|
||
_db.addBlob(dbBlob);
|
||
|
||
dbSnapshotChunk.snapshotId = idSnapshot;
|
||
dbSnapshotChunk.chunkIndex = chunk.index;
|
||
dbSnapshotChunk.offset = chunk.offset;
|
||
dbSnapshotChunk.sha256 = chunk.sha256;
|
||
|
||
_db.addSnapshotChunk(dbSnapshotChunk);
|
||
}
|
||
|
||
ok = true;
|
||
return new Snapshot(_db, idSnapshot);
|
||
}
|
||
|
||
/// Удаляет все снимки по имени файла.
|
||
long removeSnapshots(string file)
|
||
{
|
||
return _db.deleteSnapshots(file);
|
||
}
|
||
|
||
/// Удаляет конкретный снимок по объекту `Snapshot`.
|
||
bool removeSnapshot(Snapshot snapshot)
|
||
{
|
||
return removeSnapshot(snapshot.id);
|
||
}
|
||
|
||
/// Удаляет снимок по id.
|
||
bool removeSnapshot(long idSnapshot)
|
||
{
|
||
return _db.deleteSnapshot(idSnapshot) == idSnapshot;
|
||
}
|
||
|
||
/// Возвращает `Snapshot` по id.
|
||
Snapshot getSnapshot(long idSnapshot)
|
||
{
|
||
return new Snapshot(_db, idSnapshot);
|
||
}
|
||
|
||
/// Возвращает список снимков (опционально фильтр по имени файла).
|
||
Snapshot[] getSnapshots(string file = string.init)
|
||
{
|
||
Snapshot[] snapshots;
|
||
foreach (snapshot; _db.getSnapshots(file))
|
||
{
|
||
snapshots ~= new Snapshot(_db, snapshot);
|
||
}
|
||
return snapshots;
|
||
}
|
||
|
||
/// Версия библиотеки.
|
||
string getVersion() const @safe nothrow
|
||
{
|
||
import cdcdb.version_ : cdcdbVersion;
|
||
|
||
return cdcdbVersion;
|
||
}
|
||
}
|