1
0
Fork 0
forked from dlang/cdcdb
cdcdb/source/cdcdb/storage.d

249 lines
7.5 KiB
D
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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