Skip to content

Commit 4f42a80

Browse files
sunnylqmclaude
andcommitted
feat: diffWindow accepts optional windowSize parameter
(oldPath, newPath, outDiffPath[, windowSize][, cb]) — sliding-window byte size over old data, default 2MB. Larger windows catch longer-distance content moves at roughly linear additional memory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ad1dc1c commit 4f42a80

7 files changed

Lines changed: 88 additions & 20 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ in-memory `diff()` for the same inputs; prefer `diff()` when memory allows.
4646
In sync mode returns `outDiffPath`; async callback signature is
4747
`(err, outDiffPath)`.
4848

49-
### diffWindow(oldPath, newPath, outDiffPath[, cb])
49+
### diffWindow(oldPath, newPath, outDiffPath[, windowSize][, cb])
5050

5151
Create a **single-format** (same wire format as `diff()`) patch using window
5252
mode: big covers come from streaming block matching, then residuals are
@@ -56,6 +56,9 @@ stays at the streaming tier — usually a much smaller patch than
5656
`diffSingleStream()` for the same inputs. The output applies with `patch()`,
5757
`patchSingleStream()`, and any existing single-format apply side. In sync mode
5858
returns `outDiffPath`; async callback signature is `(err, outDiffPath)`.
59+
`windowSize` is the sliding-window byte size over the old data (default 2MB);
60+
a larger window catches longer-distance content moves at roughly linear
61+
additional memory.
5962

6063
### patchSingleStream(oldPath, diffPath, outNewPath[, cb])
6164

index.d.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,23 @@ export interface NativeAddon {
3838
outNewPath: string,
3939
cb: StreamCallback
4040
): void;
41-
diffWindow(oldPath: string, newPath: string, outDiffPath: string): string;
4241
diffWindow(
4342
oldPath: string,
4443
newPath: string,
4544
outDiffPath: string,
45+
windowSize?: number
46+
): string;
47+
diffWindow(
48+
oldPath: string,
49+
newPath: string,
50+
outDiffPath: string,
51+
cb: StreamCallback
52+
): void;
53+
diffWindow(
54+
oldPath: string,
55+
newPath: string,
56+
outDiffPath: string,
57+
windowSize: number,
4658
cb: StreamCallback
4759
): void;
4860
}
@@ -110,18 +122,28 @@ export function patchSingleStream(
110122
): void;
111123

112124
// window 模式生成 HDIFFSF20 single 格式 patch:匹配质量接近内存版
113-
// diff(),内存占用保持流式档;产物用 patch()/patchSingleStream() 应用
125+
// diff(),内存占用保持流式档;产物用 patch()/patchSingleStream() 应用。
126+
// windowSize 为 old 数据滑动窗口字节数(缺省 2MB),调大可捕获更长距离
127+
// 的内容移动,内存占用近似线性增长。
114128
export function diffWindow(
115129
oldPath: string,
116130
newPath: string,
117-
outDiffPath: string
131+
outDiffPath: string,
132+
windowSize?: number
118133
): string;
119134
export function diffWindow(
120135
oldPath: string,
121136
newPath: string,
122137
outDiffPath: string,
123138
cb: StreamCallback
124139
): void;
140+
export function diffWindow(
141+
oldPath: string,
142+
newPath: string,
143+
outDiffPath: string,
144+
windowSize: number,
145+
cb: StreamCallback
146+
): void;
125147

126148
declare const hdiffpatch: {
127149
native: NativeAddon;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-hdiffpatch",
3-
"version": "2.1.0",
3+
"version": "2.2.0",
44
"description": "hdiffpatch port to node.js",
55
"main": "index.js",
66
"types": "index.d.ts",

src/hdiff.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ void hdiff_stream(const char* oldPath,const char* newPath,const char* outDiffPat
145145
streams.closeAllOrThrow();
146146
}
147147

148-
void hdiff_window(const char* oldPath,const char* newPath,const char* outDiffPath){
148+
void hdiff_window(const char* oldPath,const char* newPath,const char* outDiffPath,
149+
size_t windowSize){
149150
if (!oldPath || !newPath || !outDiffPath) {
150151
throw std::runtime_error("Invalid file path.");
151152
}
@@ -158,13 +159,15 @@ void hdiff_window(const char* oldPath,const char* newPath,const char* outDiffPat
158159
streams.openInputs(oldPath, newPath);
159160
streams.openDiffOut(outDiffPath);
160161

161-
// window 模式:大块流式匹配拿大 cover,再在 old 数据的滑动窗口
162-
// (默认 2MB)内做后缀串精修。除 patchStepMemSize/匹配分沿用本库
163-
// 固定值外,其余参数取 v5 默认。
162+
// window 模式:大块流式匹配拿大 cover,再在 old 数据的滑动窗口内做
163+
// 后缀串精修。窗口默认 2MB,可调大以捕获更长距离的内容移动;
164+
// kSegSize 传 0 由上游自动取 windowSize/64。除 patchStepMemSize/
165+
// 匹配分沿用本库固定值外,其余参数取 v5 默认。
166+
if (windowSize == 0) windowSize = kDefaultWindowOldSize;
164167
create_single_compressed_diff_window(&streams.newStream.base, &streams.oldStream.base,
165168
&streams.diffOutStream.base,
166169
&compressPlugin.base, kPatchStepMemSize,
167-
kDefaultWindowOldSize, 0,
170+
windowSize, 0,
168171
kDefaultBigCoverSize, kMatchWindowsBlockSize_default,
169172
kDefaultFastMatchBlockSize,
170173
kSingleMatchScore);

src/hdiff.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ void hdiff_stream(const char* oldPath,const char* newPath,const char* outDiffPat
1616
// 任何既有 single 应用端可直接使用)
1717
void hdiff_single_stream(const char* oldPath,const char* newPath,const char* outDiffPath);
1818
// HDIFFSF20 single 格式的 window 模式生成:大块流式匹配 + 窗口内后缀串
19-
// 精修,匹配质量接近内存版而内存占用保持流式档;产物与 diff() 同格式
20-
void hdiff_window(const char* oldPath,const char* newPath,const char* outDiffPath);
19+
// 精修,匹配质量接近内存版而内存占用保持流式档;产物与 diff() 同格式。
20+
// windowSize 为 old 数据滑动窗口字节数,0 表示用默认值(2MB);窗口越大
21+
// 能捕获越长距离的内容移动,内存占用近似随之线性增长。
22+
void hdiff_window(const char* oldPath,const char* newPath,const char* outDiffPath,
23+
size_t windowSize=0);
2124

2225
#endif

src/main.cc

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -472,16 +472,19 @@ namespace hdiffpatchNode
472472
DiffWindowAsyncWorker(Napi::Function& callback,
473473
std::string oldPath,
474474
std::string newPath,
475-
std::string outDiffPath)
475+
std::string outDiffPath,
476+
size_t windowSize)
476477
: Napi::AsyncWorker(callback),
477478
oldPath_(std::move(oldPath)),
478479
newPath_(std::move(newPath)),
479-
outDiffPath_(std::move(outDiffPath)) {
480+
outDiffPath_(std::move(outDiffPath)),
481+
windowSize_(windowSize) {
480482
}
481483

482484
void Execute() override {
483485
try {
484-
hdiff_window(oldPath_.c_str(), newPath_.c_str(), outDiffPath_.c_str());
486+
hdiff_window(oldPath_.c_str(), newPath_.c_str(), outDiffPath_.c_str(),
487+
windowSize_);
485488
} catch (const std::exception& e) {
486489
SetError(e.what());
487490
}
@@ -503,6 +506,7 @@ namespace hdiffpatchNode
503506
std::string oldPath_;
504507
std::string newPath_;
505508
std::string outDiffPath_;
509+
size_t windowSize_;
506510
};
507511

508512
// ============ 同步/异步 diffSingleStream ============
@@ -546,6 +550,9 @@ namespace hdiffpatchNode
546550
// single 格式(HDIFFSF20)的 window 模式生成:大块流式匹配 + 窗口内
547551
// 后缀串精修,匹配质量接近内存版 diff() 而内存占用保持流式档。
548552
// 产物与 diff()/diffSingleStream() 同格式,既有应用端可直接应用。
553+
// 签名:(oldPath, newPath, outDiffPath[, windowSize][, cb])
554+
// windowSize 为 old 数据滑动窗口字节数(缺省 2MB),调大可捕获更长
555+
// 距离的内容移动,内存占用近似线性增长。
549556
Napi::Value diffWindow(const Napi::CallbackInfo& info) {
550557
Napi::Env env = info.Env();
551558

@@ -556,22 +563,36 @@ namespace hdiffpatchNode
556563
!getStringUtf8(info[0], oldPath) ||
557564
!getStringUtf8(info[1], newPath) ||
558565
!getStringUtf8(info[2], outDiffPath)) {
559-
Napi::TypeError::New(env, "Invalid arguments: expected (oldPath, newPath, outDiffPath).")
566+
Napi::TypeError::New(env, "Invalid arguments: expected (oldPath, newPath, outDiffPath[, windowSize][, cb]).")
560567
.ThrowAsJavaScriptException();
561568
return env.Undefined();
562569
}
563570

564-
if (info.Length() > 3 && info[3].IsFunction()) {
565-
Napi::Function callback = info[3].As<Napi::Function>();
571+
size_t windowSize = 0; // 0 = 使用默认窗口(2MB)
572+
size_t argIdx = 3;
573+
if (info.Length() > argIdx && info[argIdx].IsNumber()) {
574+
double raw = info[argIdx].As<Napi::Number>().DoubleValue();
575+
if (!std::isfinite(raw) || raw < 0 ||
576+
raw > static_cast<double>(std::numeric_limits<size_t>::max())) {
577+
Napi::TypeError::New(env, "Invalid windowSize: expected a non-negative integer.")
578+
.ThrowAsJavaScriptException();
579+
return env.Undefined();
580+
}
581+
windowSize = static_cast<size_t>(raw);
582+
argIdx++;
583+
}
584+
585+
if (info.Length() > argIdx && info[argIdx].IsFunction()) {
586+
Napi::Function callback = info[argIdx].As<Napi::Function>();
566587
DiffWindowAsyncWorker* worker = new DiffWindowAsyncWorker(
567-
callback, oldPath, newPath, outDiffPath
588+
callback, oldPath, newPath, outDiffPath, windowSize
568589
);
569590
worker->Queue();
570591
return env.Undefined();
571592
}
572593

573594
try {
574-
hdiff_window(oldPath.c_str(), newPath.c_str(), outDiffPath.c_str());
595+
hdiff_window(oldPath.c_str(), newPath.c_str(), outDiffPath.c_str(), windowSize);
575596
} catch (const std::exception& e) {
576597
Napi::Error::New(env, e.what()).ThrowAsJavaScriptException();
577598
return env.Undefined();

test/test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ console.log(
9090
" ✓ diffWindow output (" + winDiffData.length + "B vs diff " + diffResult.length +
9191
"B) applies via patch() and patchSingleStream()"
9292
);
93+
// 显式 windowSize(8MB)同步调用
94+
var winDiff8Path = path.join(ssDir, "win8.diff");
95+
hdiffpatch.diffWindow(ssOldPath, ssNewPath, winDiff8Path, 8 * 1024 * 1024);
96+
var winDiff8Data = fs.readFileSync(winDiff8Path);
97+
assert.strictEqual(winDiff8Data.slice(0, 9).toString("latin1"), "HDIFFSF20");
98+
assert.deepStrictEqual(hdiffpatch.patch(oldData, winDiff8Data), newData);
99+
assert.throws(() => hdiffpatch.diffWindow(ssOldPath, ssNewPath, winDiff8Path, -1));
100+
console.log(" ✓ diffWindow with explicit windowSize works, invalid size throws");
93101

94102
console.log("\nTest 6: Single-compressed patchSingleStream (file paths)...");
95103
var tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "hdiffpatch-"));
@@ -165,6 +173,14 @@ async function runAsyncTests() {
165173
var asyncWinDiffPath = path.join(tempDir, "async-win.diff");
166174
assert.strictEqual(await diffWindowAsync(oldPath, newPath, asyncWinDiffPath), asyncWinDiffPath);
167175
assert.deepStrictEqual(hdiffpatch.patch(oldData, fs.readFileSync(asyncWinDiffPath)), newData);
176+
// 带 windowSize 的异步形态:(old, new, out, windowSize, cb)
177+
var asyncWin8Path = path.join(tempDir, "async-win8.diff");
178+
await new Promise((resolve, reject) => {
179+
hdiffpatch.diffWindow(oldPath, newPath, asyncWin8Path, 4 * 1024 * 1024, (err, out) =>
180+
err ? reject(err) : resolve(out)
181+
);
182+
});
183+
assert.deepStrictEqual(hdiffpatch.patch(oldData, fs.readFileSync(asyncWin8Path)), newData);
168184
await assert.rejects(
169185
() => diffWindowAsync(path.join(tempDir, "no-such.bin"), newPath, asyncWinDiffPath)
170186
);

0 commit comments

Comments
 (0)