cdcdb/source/cdcdb/snapshot.d

284 lines
7.8 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.snapshot;
import cdcdb.dblite;
import zstd : uncompress;
import std.digest.sha : SHA256, digest;
import std.datetime : DateTime;
import std.exception : enforce;
/**
* Чтение снимка и управление его жизненным циклом.
*
* Класс собирает полный файл из чанков, хранящихся через `DBLite`,
* проверяет целостность (SHA-256 каждого чанка и итогового файла)
* и предоставляет безопасное удаление записи о снимке.
*
* Пример:
* ---
* auto s1 = new Snapshot(db, snapshotId);
* auto bytes = s1.data(); // материализовать весь контент в память
*
* // или потоково, без крупной аллокации:
* s1.data((const(ubyte)[] part) {
* // обработать part
* });
* ---
*
* Заметки:
* - Все проверки целостности обязательны; любое расхождение вызывает исключение.
* - Для очень больших файлов предпочтительнее потоковый вариант с делегатом.
*/
final class Snapshot
{
private:
DBLite _db;
DBSnapshot _snapshot;
/// Возвращает исходные байты чанка с учётом возможного сжатия и проверкой хеша.
const(ubyte)[] getBytes(const ref DBSnapshotChunkData chunk)
{
ubyte[] bytes;
if (chunk.zstd)
{
enforce(chunk.zSize == chunk.content.length,
"Размер сжатого чанка не совпадает с ожидаемым значением");
bytes = cast(ubyte[]) uncompress(chunk.content);
}
else
{
bytes = chunk.content.dup;
}
enforce(chunk.size == bytes.length, "Исходный размер чанка не совпадает с ожидаемым значением");
enforce(chunk.sha256 == digest!SHA256(bytes), "Хеш чанка не совпадает");
return bytes;
}
public:
/// Создать `Snapshot` из уже загруженной строки `DBSnapshot`.
///
/// Параметры:
/// dblite = хэндл базы данных
/// dbSnapshot = метаданные снимка, полученные ранее
this(DBLite dblite, DBSnapshot dbSnapshot)
{
_db = dblite;
_snapshot = dbSnapshot;
}
/// Создать `Snapshot`, подгрузив метаданные из базы.
///
/// Параметры:
/// dblite = хэндл базы данных
/// idSnapshot = идентификатор снимка
this(DBLite dblite, long idSnapshot)
{
_db = dblite;
_snapshot = _db.getSnapshot(idSnapshot);
}
/// Материализует полный контент файла в память.
///
/// Собирает чанки по порядку, проверяет SHA-256 каждого чанка и
/// итоговый SHA-256 файла (`snapshots.sha256`).
///
/// Возвращает: новый буфер `ubyte[]` с полным содержимым.
///
/// Бросает: Exception при любой ошибке целостности.
ubyte[] data()
{
auto chunks = _db.getChunks(_snapshot.id);
ubyte[] content;
content.reserve(_snapshot.sourceLength);
auto fctx = SHA256();
foreach (chunk; chunks)
{
const(ubyte)[] bytes = getBytes(chunk);
content ~= bytes;
fctx.put(bytes);
}
enforce(_snapshot.sha256 == fctx.finish(), "Хеш итогового файла не совпадает");
return content;
}
/// Потоково передаёт содержимое файла в заданный приёмник.
///
/// Избегает одной большой аллокации: чанк декодируется, проверяется
/// и передаётся в `sink` по порядку.
///
/// Параметры:
/// sink = делегат, вызываемый для каждого проверенного чанка.
///
/// Бросает: Exception при любой ошибке целостности.
void data(void delegate(const(ubyte)[]) sink)
{
auto chunks = _db.getChunks(_snapshot.id);
auto fctx = SHA256();
foreach (chunk; chunks)
{
const(ubyte)[] bytes = getBytes(chunk);
sink(bytes);
fctx.put(bytes);
}
enforce(_snapshot.sha256 == fctx.finish(), "Хеш итогового файла не совпадает");
}
/// Удаляет снимок из базы в транзакции.
///
/// Открывает транзакцию IMMEDIATE, удаляет запись о снимке и коммитит.
/// В случае ошибки откатывает.
///
/// Возвращает: `true`, если запись была удалена.
///
/// Примечание: не выполняет сборку мусора по блобам.
bool remove()
{
_db.beginImmediate();
bool ok;
scope (exit)
{
if (!ok)
_db.rollback();
}
scope (success)
{
_db.commit();
}
long idDeleted = _db.deleteSnapshot(_snapshot.id);
ok = true;
return _snapshot.id == idDeleted;
}
// -----------------------------
// Доступ к метаданным снимка
// -----------------------------
/// ID снимка (PRIMARY KEY).
@property long id() const nothrow @safe
{
return _snapshot.id;
}
/// Имя файла (из таблицы `files`).
@property string file() const nothrow @safe
{
return _snapshot.file;
}
/// Время создания (UTC).
@property DateTime created() const @safe
{
return _snapshot.createdUtc;
}
/// Длина исходного файла (байты).
@property long length() const nothrow @safe
{
return _snapshot.sourceLength;
}
/// Ожидаемый SHA-256 всего файла (сырые 32 байта).
@property ubyte[32] sha256() const nothrow @safe
{
return _snapshot.sha256;
}
/// Статус снимка (строкой).
@property string status() const
{
import std.conv : to;
return _snapshot.status.to!string;
}
/// Необязательное описание.
@property string description() const nothrow @safe
{
return _snapshot.description;
}
/// FastCDC: минимальный размер чанка.
@property long algoMin() const nothrow @safe
{
return _snapshot.algoMin;
}
/// FastCDC: целевой (нормальный) размер чанка.
@property long algoNormal() const nothrow @safe
{
return _snapshot.algoNormal;
}
/// FastCDC: максимальный размер чанка.
@property long algoMax() const nothrow @safe
{
return _snapshot.algoMax;
}
/// FastCDC: строгая маска.
@property long maskS() const nothrow @safe
{
return _snapshot.maskS;
}
/// FastCDC: слабая маска.
@property long maskL() const nothrow @safe
{
return _snapshot.maskL;
}
/// UID процесса (effective).
@property long uid() const nothrow @safe
{
return _snapshot.uid;
}
/// Real UID процесса.
@property long ruid() const nothrow @safe
{
return _snapshot.ruid;
}
/// Имя пользователя для `uid`.
@property string uidName() const nothrow @safe
{
return _snapshot.uidName;
}
/// Имя пользователя для `ruid`.
@property string ruidName() const nothrow @safe
{
return _snapshot.ruidName;
}
/// Имя процесса (из таблицы `processes`).
@property string process() const nothrow @safe
{
return _snapshot.process;
}
/// Удобный флаг: снимок «готов».
@property bool isReady() const nothrow @safe
{
return _snapshot.status == SnapshotStatus.ready;
}
/// Удобный флаг: снимок «в процессе».
@property bool isPending() const nothrow @safe
{
return _snapshot.status == SnapshotStatus.pending;
}
}