166 lines
5.4 KiB
D
166 lines
5.4 KiB
D
module xdiff_mmfile;
|
|
|
|
import std.exception : enforce;
|
|
import xdiff_init : ensureInit, initMmfile;
|
|
import xdiff_mmblocks : MMBlocks;
|
|
import xdiff;
|
|
|
|
final class MMFile
|
|
{
|
|
private:
|
|
mmfile_t _inner;
|
|
bool _owned = true;
|
|
|
|
public:
|
|
this()
|
|
{
|
|
_inner = initMmfile(0);
|
|
}
|
|
|
|
static MMFile fromBytes(const(ubyte)[] data)
|
|
{
|
|
auto f = new MMFile;
|
|
if (data.length > 0)
|
|
{
|
|
auto wrote = xdl_write_mmfile(&f._inner, data.ptr, cast(long)data.length);
|
|
enforce(wrote == cast(long)data.length, "xdl_write_mmfile wrote less than requested");
|
|
}
|
|
return f;
|
|
}
|
|
|
|
~this()
|
|
{
|
|
if (_owned && _inner.head !is null)
|
|
xdl_free_mmfile(&_inner);
|
|
|
|
_inner.head = null; _inner.tail = null;
|
|
_inner.rcur = null; _inner.wcur = null;
|
|
_inner.fsize = 0; _inner.bsize = 0;
|
|
}
|
|
|
|
size_t size() { return cast(size_t)xdl_mmfile_size(&_inner); }
|
|
bool isCompact() const { return xdl_mmfile_iscompact(cast(mmfile_t*)&_inner) != 0; }
|
|
|
|
const(ubyte)[] asSlice() const
|
|
{
|
|
enforce(isCompact(), "MMFile must be compact for asSlice");
|
|
auto h = _inner.head;
|
|
if (h is null || h.size <= 0) return (cast(ubyte*)null)[0 .. 0];
|
|
return (cast(const(ubyte)*)h.ptr)[0 .. cast(size_t)h.size];
|
|
}
|
|
|
|
ubyte[] asSliceMut()
|
|
{
|
|
enforce(isCompact(), "MMFile must be compact for asSliceMut");
|
|
auto h = _inner.head;
|
|
if (h is null || h.size <= 0) return (cast(ubyte*)null)[0 .. 0];
|
|
return (cast(ubyte*)h.ptr)[0 .. cast(size_t)h.size];
|
|
}
|
|
|
|
alias MMPatch = MMBlocks;
|
|
|
|
/// Сформировать patch (diff self → other)
|
|
MMPatch computePatch(ref MMFile other)
|
|
{
|
|
auto patch = new MMBlocks();
|
|
|
|
static extern(C) int emitToPatch(void* priv, mmbuffer_t* bufs, int num)
|
|
{
|
|
auto target = cast(MMBlocks)priv; // класс
|
|
foreach (i; 0 .. num)
|
|
{
|
|
auto b = bufs + i;
|
|
auto wrote = xdl_writem_mmfile(target.mmfilePtr(), b, 1);
|
|
if (wrote != (*b).size) return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
xpparam_t xpp; xpp.flags = 0;
|
|
xdemitconf_t xec; xec.ctxlen = 3;
|
|
xdemitcb_t ecb; ecb.priv = cast(void*)patch; ecb.outf = &emitToPatch;
|
|
|
|
auto rc = xdl_diff(&_inner, &other._inner, &xpp, &xec, &ecb);
|
|
enforce(rc == 0, "xdl_diff failed");
|
|
return patch;
|
|
}
|
|
|
|
struct PatchResult { bool success; MMFile patched; MMFile rejected; }
|
|
|
|
/// Применить patch к self
|
|
PatchResult applyPatch(ref MMPatch patch)
|
|
{
|
|
patch.toCompact();
|
|
|
|
auto acc = new MMBlocks();
|
|
auto rej = new MMBlocks();
|
|
|
|
static extern(C) int emitTo(void* priv, mmbuffer_t* bufs, int num)
|
|
{
|
|
auto target = cast(MMBlocks)priv; // класс
|
|
long expect = 0; foreach (i; 0 .. num) expect += (bufs + i).size;
|
|
auto wrote = xdl_writem_mmfile(target.mmfilePtr(), bufs, num);
|
|
return (wrote == expect) ? 0 : -1;
|
|
}
|
|
|
|
xdemitcb_t accCb; accCb.priv = cast(void*)acc; accCb.outf = &emitTo;
|
|
xdemitcb_t rejCb; rejCb.priv = cast(void*)rej; rejCb.outf = &emitTo;
|
|
|
|
auto rc = xdl_patch(&_inner, patch.mmfilePtr(), XDL_PATCH_NORMAL, &accCb, &rejCb);
|
|
enforce(rc == 0 || rc == 1, "xdl_patch failed");
|
|
|
|
auto accFile = MMFile.fromBlocksMoved(acc);
|
|
auto rejFile = MMFile.fromBlocksMoved(rej);
|
|
|
|
bool ok = (rejFile.size() == 0);
|
|
return PatchResult(ok, accFile, rejFile);
|
|
}
|
|
|
|
/// 3-way merge: коллбэки nothrow, чтобы не бросать через C
|
|
void merge3Raw(
|
|
ref MMFile f1,
|
|
ref MMFile f2,
|
|
scope int delegate(const(ubyte)[]) nothrow acceptCb,
|
|
scope int delegate(const(ubyte)[]) nothrow rejectCb)
|
|
{
|
|
static extern(C) int emitAccept(void* priv, mmbuffer_t* bufs, int num)
|
|
{
|
|
auto d = cast(int delegate(const(ubyte)[]) nothrow*)priv;
|
|
foreach (i; 0 .. num)
|
|
{
|
|
auto b = bufs + i;
|
|
auto slice = (cast(const(ubyte)*)((*b).ptr))[0 .. cast(size_t)((*b).size)];
|
|
auto r = (*d)(slice); if (r != 0) return r;
|
|
}
|
|
return 0;
|
|
}
|
|
static extern(C) int emitReject(void* priv, mmbuffer_t* bufs, int num)
|
|
{
|
|
auto d = cast(int delegate(const(ubyte)[]) nothrow*)priv;
|
|
foreach (i; 0 .. num)
|
|
{
|
|
auto b = bufs + i;
|
|
auto slice = (cast(const(ubyte)*)((*b).ptr))[0 .. cast(size_t)((*b).size)];
|
|
auto r = (*d)(slice); if (r != 0) return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
auto a = acceptCb, r = rejectCb;
|
|
xdemitcb_t accCb; accCb.priv = cast(void*)&a; accCb.outf = &emitAccept;
|
|
xdemitcb_t rejCb; rejCb.priv = cast(void*)&r; rejCb.outf = &emitReject;
|
|
|
|
auto rc = xdl_merge3(&_inner, &f1._inner, &f2._inner, &accCb, &rejCb);
|
|
enforce(rc == 0, "xdl_merge3 failed");
|
|
}
|
|
|
|
/// «Move» из MMBlocks: забрать владение внутренними блоками
|
|
static MMFile fromBlocksMoved(MMBlocks b)
|
|
{
|
|
b.toCompact();
|
|
auto f = new MMFile;
|
|
f._inner = *b.mmfilePtr(); // копируем заголовок (указатели на блоки)
|
|
b.disarm(); // источник больше не владеет
|
|
return f;
|
|
}
|
|
}
|