dxdiff/source/xdiff_mmfile.d
2025-08-31 16:47:55 +03:00

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