1
0
Fork 0
forked from dlang/cdcdb

Переписана под ООП

This commit is contained in:
Alexander Zhirov 2025-09-12 23:37:12 +03:00
parent 46138c032a
commit 8a9142234e
Signed by: alexander
GPG key ID: C8D8BE544A27C511
14 changed files with 372 additions and 526 deletions

View file

@ -11,8 +11,7 @@
"zstd": "~>0.2.1"
},
"stringImportPaths": [
"source/cdcdb/db",
"source/cdcdb/cdc"
"source/cdcdb"
],
"configurations": [
{

View file

@ -1,185 +0,0 @@
module cdcdb.cdc.cas;
import cdcdb.db;
import cdcdb.cdc.core;
import zstd;
import std.digest.sha : SHA256, digest;
import std.format : format;
import std.exception : enforce;
// Content-Addressable Storage (Контентно-адресуемая система хранения)
// CAS-хранилище со снапшотами
final class CAS
{
private:
DBLite _db;
bool _zstd;
size_t _minSize;
size_t _normalSize;
size_t _maxSize;
size_t _maskS;
size_t _maskL;
CDC _cdc;
public:
this(
string database,
bool zstd = false,
size_t busyTimeout = 3000,
size_t maxRetries = 3,
size_t minSize = 256,
size_t normalSize = 512,
size_t maxSize = 1024,
size_t maskS = 0xFF,
size_t maskL = 0x0F
) {
_db = new DBLite(database, busyTimeout, maxRetries);
_zstd = zstd;
_minSize = minSize;
_normalSize = normalSize;
_maxSize = maxSize;
_maskS = maskS;
_maskL = maskL;
_cdc = new CDC(_minSize, _normalSize, _maxSize, _maskS, _maskL);
}
size_t newSnapshot(string label, const(ubyte)[] data, string description = string.init)
{
if (data.length == 0) {
throw new Exception("Данные имеют нулевой размер");
}
ubyte[32] sha256 = digest!SHA256(data);
// Если последний снимок файла соответствует текущему состоянию
if (_db.isLast(label, sha256)) return 0;
Snapshot snapshot;
snapshot.label = label;
snapshot.sha256 = sha256;
snapshot.description = description;
snapshot.sourceLength = data.length;
snapshot.algoMin = _minSize;
snapshot.algoNormal = _normalSize;
snapshot.algoMax = _maxSize;
snapshot.maskS = _maskS;
snapshot.maskL = _maskL;
_db.beginImmediate();
bool ok;
scope (exit)
{
if (!ok)
_db.rollback();
}
scope (success)
{
_db.commit();
}
auto idSnapshot = _db.addSnapshot(snapshot);
SnapshotChunk snapshotChunk;
Blob blob;
blob.zstd = _zstd;
// Разбить на фрагменты
Chunk[] chunks = _cdc.split(data);
// Запись фрагментов в БД
foreach (chunk; chunks)
{
blob.sha256 = chunk.sha256;
blob.size = chunk.size;
auto content = data[chunk.offset .. chunk.offset + chunk.size];
if (_zstd) {
ubyte[] zBytes = compress(content, 22);
size_t zSize = zBytes.length;
ubyte[32] zHash = digest!SHA256(zBytes);
blob.zSize = zSize;
blob.zSha256 = zHash;
blob.content = zBytes;
} else {
blob.content = content.dup;
}
// Запись фрагментов
_db.addBlob(blob);
snapshotChunk.snapshotId = idSnapshot;
snapshotChunk.chunkIndex = chunk.index;
snapshotChunk.offset = chunk.offset;
snapshotChunk.sha256 = chunk.sha256;
// Привязка фрагментов к снимку
_db.addSnapshotChunk(snapshotChunk);
}
ok = true;
return idSnapshot;
}
Snapshot[] getSnapshots(string label = string.init)
{
return _db.getSnapshots(label);
}
ubyte[] getSnapshotData(const ref Snapshot snapshot)
{
auto dataChunks = _db.getChunks(snapshot.id);
ubyte[] content;
foreach (chunk; dataChunks) {
ubyte[] bytes;
if (chunk.zstd) {
enforce(chunk.zSize == chunk.content.length, "Размер сжатого фрагмента не соответствует ожидаемому");
bytes = cast(ubyte[]) uncompress(chunk.content);
} else {
bytes = chunk.content;
}
enforce(chunk.size == bytes.length, "Оригинальный размер не соответствует ожидаемому");
content ~= bytes;
}
enforce(snapshot.sha256 == digest!SHA256(content), "Хеш-сумма файла не совпадает");
return content;
}
void removeSnapshot(const ref Snapshot snapshot)
{
_db.beginImmediate();
bool ok;
scope (exit)
{
if (!ok)
_db.rollback();
}
scope (success)
{
_db.commit();
}
_db.deleteSnapshot(snapshot.id);
ok = true;
}
string getVersion() const @safe nothrow {
import cdcdb.version_;
return cdcdbVersion;
}
}

View file

@ -1,3 +0,0 @@
module cdcdb.cdc;
public import cdcdb.cdc.cas;

View file

@ -1,4 +1,4 @@
module cdcdb.cdc.core;
module cdcdb.core;
import std.digest.sha : SHA256, digest;

View file

@ -1,4 +0,0 @@
module cdcdb.db;
public import cdcdb.db.dblite;
public import cdcdb.db.types;

View file

@ -1,197 +0,0 @@
# Схемы базы данных для хранения снимков (фрагментов)
## Структура базы данных
```mermaid
erDiagram
%% Композитный PK у SNAPSHOT_CHUNKS: (snapshot_id, chunk_index)
SNAPSHOTS {
int id PK
string label
string created_utc
int source_length
int algo_min
int algo_normal
int algo_max
int mask_s
int mask_l
string status
}
BLOBS {
string sha256 PK
int size
blob content
string created_utc
}
SNAPSHOT_CHUNKS {
int snapshot_id FK
int chunk_index
int offset
int size
string sha256 FK
}
%% Связи и поведение внешних ключей
SNAPSHOTS ||--o{ SNAPSHOT_CHUNKS : "1:N, ON DELETE CASCADE"
BLOBS ||--o{ SNAPSHOT_CHUNKS : "1:N, ON DELETE RESTRICT"
```
## Схема последовательности записи в базу данных
```mermaid
sequenceDiagram
autonumber
participant APP as Приложение
participant CH as Разбиение на чанки (FastCDC)
participant HS as Хеширование (SHA-256)
participant DB as База данных (SQLite)
Note over APP,DB: Подготовка
APP->>DB: Открывает соединение, включает PRAGMA (WAL, foreign_keys=ON)
APP->>DB: BEGIN IMMEDIATE (начать транзакцию с блокировкой на запись)
Note over APP,DB: Создание метаданных снимка
APP->>DB: INSERT INTO snapshots(label, source_length, algo_min, algo_normal, algo_max, mask_s, mask_l, status='pending')
DB-->>APP: id снимка = last_insert_rowid()
Note over APP,CH: Поток файла → чанки
APP->>CH: Читает файл, передает параметры FastCDC (min/normal/max, mask_s/mask_l)
loop Для каждого чанка в порядке следования
CH-->>APP: Возвращает {chunk_index, offset, size, bytes}
Note over APP,HS: Хеш содержимого
APP->>HS: Вычисляет SHA-256(bytes)
HS-->>APP: digest (sha256)
Note over APP,DB: Дедупликация контента
APP->>DB: SELECT 1 FROM blobs WHERE sha256 = ?
alt Блоб отсутствует
APP->>DB: INSERT INTO blobs(sha256, size, content)
DB-->>APP: OK
else Блоб уже есть
DB-->>APP: Найден (пропускаем вставку содержимого)
end
Note over APP,DB: Привязка чанка к снимку
APP->>DB: INSERT INTO snapshot_chunks(snapshot_id, chunk_index, offset, size, sha256)
DB-->>APP: OK (PK: (snapshot_id, chunk_index))
end
Note over APP,DB: Валидация и завершение
APP->>DB: SELECT SUM(size) FROM snapshot_chunks WHERE snapshot_id = ?
DB-->>APP: total_size
alt total_size == snapshots.source_length
APP->>DB: UPDATE snapshots SET status='ready' WHERE id = ?
APP->>DB: COMMIT
DB-->>APP: Транзакция зафиксирована
else Несоответствие размеров или ошибка
APP->>DB: ROLLBACK
DB-->>APP: Откат изменений
APP-->>APP: Логирует ошибку, возвращает код/исключение
end
```
## Схема последовательности восстановления из базы данных
```mermaid
sequenceDiagram
autonumber
participant APP as Приложение
participant DB as База данных (SQLite)
participant FS as Целевой файл
participant HS as Хеширование (опц.)
Note over APP,DB: Подготовка к чтению
APP->>DB: Открывает соединение (read), BEGIN (снимок чтения)
Note over APP,DB: Выбор снимка
APP->>DB: Находит нужный снимок по id/label, читает status и source_length
DB-->>APP: id, status, source_length
alt status == "ready"
else снимок не готов
APP-->>APP: Прерывает восстановление с ошибкой
DB-->>APP: END
end
Note over APP,DB: Получение состава снимка
APP->>DB: SELECT chunk_index, offset, size, sha256 FROM snapshot_chunks WHERE snapshot_id=? ORDER BY chunk_index
DB-->>APP: Строки чанков в порядке chunk_index
loop Для каждого чанка
APP->>DB: SELECT content, size FROM blobs WHERE sha256=?
DB-->>APP: content, blob_size
Note over APP,HS: (опц.) контроль целостности чанка
APP->>HS: Вычисляет SHA-256(content)
HS-->>APP: digest
APP-->>APP: Сверяет digest с sha256 и size с blob_size
alt offset задан
APP->>FS: Позиционируется на offset и пишет content (pwrite/seek+write)
else offset отсутствует
APP->>FS: Дописывает content в конец файла
end
end
Note over APP,DB: Финальная проверка
APP-->>APP: Суммирует размеры записанных чанков → total_size
APP->>DB: Берёт snapshots.source_length
DB-->>APP: source_length
alt total_size == source_length
APP->>FS: fsync и close
DB-->>APP: END
APP-->>APP: Успешное восстановление
else размеры не совпали
APP->>FS: Удаляет/помечает файл как повреждённый
DB-->>APP: END
APP-->>APP: Фиксирует ошибку (несоответствие сумм)
end
```
## Схема записи в БД
```mermaid
sequenceDiagram
autonumber
participant APP as Приложение
participant DB as SQLite
participant CH as Разбиение (FastCDC)
participant HS as SHA-256
Note over APP,DB: Подготовка к записи
APP->>DB: PRAGMA foreign_keys=ON
APP->>DB: BEGIN IMMEDIATE
Note over APP,DB: Метаданные снимка
APP->>DB: INSERT INTO snapshots(..., status='pending')
DB-->>APP: snap_id := last_insert_rowid()
Note over APP,CH: Поток файла → чанки (min/normal/max, mask_s/mask_l)
loop Для каждого чанка по порядку
CH-->>APP: {chunk_index, offset, size, bytes}
Note over APP,HS: Хеширование
APP->>HS: SHA-256(bytes)
HS-->>APP: sha256 (32 байта)
Note over APP,DB: Дедупликация содержимого
APP->>DB: INSERT INTO blobs(sha256,size,content) ON CONFLICT DO NOTHING
DB-->>APP: OK (новая строка или уже была)
Note over APP,DB: Привязка к снимку
APP->>DB: INSERT INTO snapshot_chunks(snapshot_id,chunk_index,offset,size,sha256)
DB-->>APP: OK (триггер ++refcount, last_seen_utc=now)
end
Note over APP,DB: Валидация и финал
APP->>DB: SELECT SUM(size) FROM snapshot_chunks WHERE snapshot_id = snap_id
DB-->>APP: total_size
alt total_size == snapshots.source_length
Note over DB: триггер mark_ready ставит status='ready'
APP->>DB: COMMIT
else несовпадение / ошибка
APP->>DB: ROLLBACK
end
```

View file

@ -1,57 +0,0 @@
module cdcdb.db.types;
import std.datetime : DateTime;
enum SnapshotStatus : int
{
pending = 0,
ready = 1
}
struct Snapshot
{
long id;
string label;
ubyte[32] sha256;
string description;
DateTime createdUtc;
long sourceLength;
long algoMin;
long algoNormal;
long algoMax;
long maskS;
long maskL;
SnapshotStatus status;
}
struct Blob
{
ubyte[32] sha256;
ubyte[32] zSha256;
long size;
long zSize;
ubyte[] content;
DateTime createdUtc;
DateTime lastSeenUtc;
long refcount;
bool zstd;
}
struct SnapshotChunk
{
long snapshotId;
long chunkIndex;
long offset;
ubyte[32] sha256;
}
struct SnapshotDataChunk {
long chunkIndex;
long offset;
long size;
ubyte[] content;
bool zstd;
long zSize;
ubyte[32] sha256;
ubyte[32] zSha256;
}

View file

@ -1,14 +1,66 @@
module cdcdb.db.dblite;
module cdcdb.dblite;
import cdcdb.db.types;
import arsd.sqlite : Sqlite, SqliteResult, DatabaseException;
import arsd.sqlite;
import std.exception : enforce;
import std.conv : to;
import std.datetime : DateTime;
import std.string : join, replace, toLower;
import std.algorithm : canFind;
import std.conv : to;
import std.format : format;
import std.exception : enforce;
enum SnapshotStatus : ubyte
{
pending = 0,
ready = 1
}
struct DBSnapshot {
long id;
string label;
ubyte[32] sha256;
string description;
DateTime createdUtc;
long sourceLength;
long algoMin;
long algoNormal;
long algoMax;
long maskS;
long maskL;
SnapshotStatus status;
}
struct DBSnapshotChunk
{
long snapshotId;
long chunkIndex;
long offset;
ubyte[32] sha256;
}
struct DBBlob
{
ubyte[32] sha256;
ubyte[32] zSha256;
long size;
long zSize;
ubyte[] content;
DateTime createdUtc;
DateTime lastSeenUtc;
long refcount;
bool zstd;
}
struct DBSnapshotChunkData {
long chunkIndex;
long offset;
long size;
ubyte[] content;
bool zstd;
long zSize;
ubyte[32] sha256;
ubyte[32] zSha256;
}
final class DBLite : Sqlite
{
@ -120,7 +172,25 @@ public:
sql("ROLLBACK");
}
long addSnapshot(Snapshot snapshot)
bool isLast(string label, ubyte[] sha256) {
auto queryResult = sql(
q{
SELECT COALESCE(
(SELECT (label = ? AND sha256 = ?)
FROM snapshots
ORDER BY created_utc DESC
LIMIT 1),
0
) AS is_last;
}, label, sha256
);
if (!queryResult.empty())
return queryResult.front()["is_last"].to!long > 0;
return false;
}
long addSnapshot(DBSnapshot snapshot)
{
auto queryResult = sql(
q{
@ -157,13 +227,14 @@ public:
return queryResult.front()["id"].to!long;
}
void addBlob(Blob blob)
bool addBlob(DBBlob blob)
{
sql(
auto queryResult = sql(
q{
INSERT INTO blobs (sha256, z_sha256, size, z_size, content, zstd)
VALUES (?,?,?,?,?,?)
ON CONFLICT (sha256) DO NOTHING
RETURNING sha256
},
blob.sha256[],
blob.zstd ? blob.zSha256[] : null,
@ -172,76 +243,28 @@ public:
blob.content,
blob.zstd.to!int
);
return !queryResult.empty();
}
void addSnapshotChunk(SnapshotChunk snapshotChunk)
bool addSnapshotChunk(DBSnapshotChunk snapshotChunk)
{
sql(
auto queryResult = sql(
q{
INSERT INTO snapshot_chunks (snapshot_id, chunk_index, offset, sha256)
VALUES(?,?,?,?)
RETURNING snapshot_id
},
snapshotChunk.snapshotId,
snapshotChunk.chunkIndex,
snapshotChunk.offset,
snapshotChunk.sha256[]
);
return !queryResult.empty();
}
bool isLast(string label, ubyte[] sha256) {
auto queryResult = sql(
q{
SELECT COALESCE(
(SELECT (label = ? AND sha256 = ?)
FROM snapshots
ORDER BY created_utc DESC
LIMIT 1),
0
) AS is_last;
}, label, sha256
);
if (!queryResult.empty())
return queryResult.front()["is_last"].to!long > 0;
return false;
}
Snapshot[] getSnapshots(string label)
{
auto queryResult = sql(
q{
SELECT id, label, sha256, description, created_utc, source_length,
algo_min, algo_normal, algo_max, mask_s, mask_l, status
FROM snapshots WHERE (length(?) = 0 OR label = ?1);
}, label
);
Snapshot[] snapshots;
foreach (row; queryResult)
{
Snapshot snapshot;
snapshot.id = row["id"].to!long;
snapshot.label = row["label"].to!string;
snapshot.sha256 = cast(ubyte[]) row["sha256"].dup;
snapshot.description = row["description"].to!string;
snapshot.createdUtc = toDateTime(row["created_utc"].to!string);
snapshot.sourceLength = row["source_length"].to!long;
snapshot.algoMin = row["algo_min"].to!long;
snapshot.algoNormal = row["algo_normal"].to!long;
snapshot.algoMax = row["algo_max"].to!long;
snapshot.maskS = row["mask_s"].to!long;
snapshot.maskL = row["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus) row["status"].to!int;
snapshots ~= snapshot;
}
return snapshots;
}
Snapshot getSnapshot(long id)
DBSnapshot getSnapshot(long id)
{
auto queryResult = sql(
q{
@ -251,7 +274,7 @@ public:
}, id
);
Snapshot snapshot;
DBSnapshot snapshot;
if (!queryResult.empty())
{
@ -274,11 +297,42 @@ public:
return snapshot;
}
void deleteSnapshot(long id) {
sql("DELETE FROM snapshots WHERE id = ?", id);
DBSnapshot[] getSnapshots(string label)
{
auto queryResult = sql(
q{
SELECT id, label, sha256, description, created_utc, source_length,
algo_min, algo_normal, algo_max, mask_s, mask_l, status
FROM snapshots WHERE (length(?) = 0 OR label = ?1);
}, label
);
DBSnapshot[] snapshots;
foreach (row; queryResult)
{
DBSnapshot snapshot;
snapshot.id = row["id"].to!long;
snapshot.label = row["label"].to!string;
snapshot.sha256 = cast(ubyte[]) row["sha256"].dup;
snapshot.description = row["description"].to!string;
snapshot.createdUtc = toDateTime(row["created_utc"].to!string);
snapshot.sourceLength = row["source_length"].to!long;
snapshot.algoMin = row["algo_min"].to!long;
snapshot.algoNormal = row["algo_normal"].to!long;
snapshot.algoMax = row["algo_max"].to!long;
snapshot.maskS = row["mask_s"].to!long;
snapshot.maskL = row["mask_l"].to!long;
snapshot.status = cast(SnapshotStatus) row["status"].to!int;
snapshots ~= snapshot;
}
return snapshots;
}
SnapshotDataChunk[] getChunks(long snapshotId)
DBSnapshotChunkData[] getChunks(long snapshotId)
{
auto queryResult = sql(
q{
@ -291,11 +345,11 @@ public:
}, snapshotId
);
SnapshotDataChunk[] sdchs;
DBSnapshotChunkData[] sdchs;
foreach (row; queryResult)
{
SnapshotDataChunk sdch;
DBSnapshotChunkData sdch;
sdch.chunkIndex = row["chunk_index"].to!long;
sdch.offset = row["offset"].to!long;
@ -311,4 +365,14 @@ public:
return sdchs;
}
long deleteSnapshot(long id) {
auto queryResult = sql("DELETE FROM snapshots WHERE id = ? RETURNING id", id);
if (queryResult.empty()) {
throw new Exception("Ошибка при удалении снимка из базы данных");
}
return queryResult.front()["id"].to!long;
}
}

View file

@ -1,3 +1,4 @@
module cdcdb;
public import cdcdb.cdc;
public import cdcdb.storage;
public import cdcdb.snapshot;

68
source/cdcdb/snapshot.d Normal file
View file

@ -0,0 +1,68 @@
module cdcdb.snapshot;
import cdcdb.dblite;
import std.exception : enforce;
final class Snapshot {
private:
DBLite _db;
DBSnapshot _snapshot;
public:
this(DBLite dblite, DBSnapshot dbSnapshot) {
_db = dblite;
_snapshot = dbSnapshot;
}
this(DBLite dblite, long idSnapshot) {
_db = dblite;
_snapshot = _db.getSnapshot(idSnapshot);
}
ubyte[] data() {
auto dataChunks = _db.getChunks(_snapshot.id);
ubyte[] content;
import zstd : uncompress;
foreach (chunk; dataChunks) {
ubyte[] bytes;
if (chunk.zstd) {
enforce(chunk.zSize == chunk.content.length, "Размер сжатого фрагмента не соответствует ожидаемому");
bytes = cast(ubyte[]) uncompress(chunk.content);
} else {
bytes = chunk.content;
}
enforce(chunk.size == bytes.length, "Оригинальный размер не соответствует ожидаемому");
content ~= bytes;
}
import std.digest.sha : SHA256, digest;
enforce(_snapshot.sha256 == digest!SHA256(content), "Хеш-сумма файла не совпадает");
return content;
}
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;
}
}

154
source/cdcdb/storage.d Normal file
View file

@ -0,0 +1,154 @@
module cdcdb.storage;
import cdcdb.dblite;
import cdcdb.core;
import cdcdb.snapshot;
final class Storage
{
private:
// Параметры работы с базой данных
DBLite _db;
bool _zstd;
// Настройки CDC механизма
CDC _cdc;
size_t _minSize;
size_t _normalSize;
size_t _maxSize;
size_t _maskS;
size_t _maskL;
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:
this(string database, bool zstd = false, size_t busyTimeout = 3000, size_t maxRetries = 3)
{
_db = new DBLite(database, busyTimeout, maxRetries);
_zstd = zstd;
initCDC();
}
void setupCDC(size_t minSize, size_t normalSize, size_t maxSize, size_t maskS, size_t maskL)
{
initCDC(minSize, normalSize, maxSize, maskS, maskL);
}
Snapshot newSnapshot(string label, const(ubyte)[] data, 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(label, sha256))
return null;
DBSnapshot dbSnapshot;
dbSnapshot.label = label;
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;
_db.beginImmediate();
bool ok;
scope (exit)
{
if (!ok)
_db.rollback();
}
scope (success)
{
_db.commit();
}
auto idSnapshot = _db.addSnapshot(dbSnapshot);
DBSnapshotChunk dbSnapshotChunk;
DBBlob dbBlob;
dbBlob.zstd = _zstd;
// Разбить на фрагменты
Chunk[] chunks = _cdc.split(data);
import zstd : compress;
// Запись фрагментов в БД
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, 22);
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;
Snapshot snapshot = new Snapshot(_db, idSnapshot);
return snapshot;
}
Snapshot[] getSnapshots(string label = string.init) {
Snapshot[] snapshots;
foreach (snapshot; _db.getSnapshots(label)) {
snapshots ~= new Snapshot(_db, snapshot);
}
return snapshots;
}
string getVersion() const @safe nothrow
{
import cdcdb.version_ : cdcdbVersion;
return cdcdbVersion;
}
}

View file

@ -6,12 +6,18 @@ import std.file : read;
void main()
{
auto cas = new CAS("/tmp/base.db", true);
cas.newSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text"));
// import std.stdio : writeln;
auto storage = new Storage("/tmp/base.db", true);
storage.newSnapshot("/tmp/text", cast(ubyte[]) read("/tmp/text"));
foreach (snapshot; cas.getSnapshots()) {
writeln(snapshot);
// if (snapshot !is null) {
// writeln(cast(string) snapshot.data);
// snapshot.remove();
// }
import std.stdio : writeln;
foreach (snapshot; storage.getSnapshots()) {
writeln(cast(string) snapshot.data);
}
// writeln(cas.getVersion);