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