trove-cli 0.0.0-stage → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +202 -2
- package/dist/adapters/file-source.d.ts +31 -0
- package/dist/adapters/file-source.js +97 -0
- package/dist/adapters/file-source.js.map +1 -0
- package/dist/adapters/index.d.ts +34 -0
- package/dist/adapters/index.js +146 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +30 -0
- package/dist/adapters/opencode.js +135 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/cli.d.ts +58 -0
- package/dist/cli.js +974 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +81 -0
- package/dist/config.js +99 -0
- package/dist/config.js.map +1 -0
- package/dist/core/archive.d.ts +27 -0
- package/dist/core/archive.js +101 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/chunker.d.ts +28 -0
- package/dist/core/chunker.js +67 -0
- package/dist/core/chunker.js.map +1 -0
- package/dist/core/clean.d.ts +36 -0
- package/dist/core/clean.js +182 -0
- package/dist/core/clean.js.map +1 -0
- package/dist/core/collect.d.ts +39 -0
- package/dist/core/collect.js +194 -0
- package/dist/core/collect.js.map +1 -0
- package/dist/core/concurrency.d.ts +2 -0
- package/dist/core/concurrency.js +16 -0
- package/dist/core/concurrency.js.map +1 -0
- package/dist/core/cursor.d.ts +31 -0
- package/dist/core/cursor.js +31 -0
- package/dist/core/cursor.js.map +1 -0
- package/dist/core/events/claude.d.ts +7 -0
- package/dist/core/events/claude.js +287 -0
- package/dist/core/events/claude.js.map +1 -0
- package/dist/core/events/codex.d.ts +6 -0
- package/dist/core/events/codex.js +149 -0
- package/dist/core/events/codex.js.map +1 -0
- package/dist/core/events/doubao.d.ts +9 -0
- package/dist/core/events/doubao.js +151 -0
- package/dist/core/events/doubao.js.map +1 -0
- package/dist/core/events/index.d.ts +6 -0
- package/dist/core/events/index.js +20 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/events/opencode.d.ts +7 -0
- package/dist/core/events/opencode.js +189 -0
- package/dist/core/events/opencode.js.map +1 -0
- package/dist/core/events/schema.d.ts +83 -0
- package/dist/core/events/schema.js +35 -0
- package/dist/core/events/schema.js.map +1 -0
- package/dist/core/layout.d.ts +26 -0
- package/dist/core/layout.js +122 -0
- package/dist/core/layout.js.map +1 -0
- package/dist/core/lessons/analyze.d.ts +40 -0
- package/dist/core/lessons/analyze.js +107 -0
- package/dist/core/lessons/analyze.js.map +1 -0
- package/dist/core/lessons/audit.d.ts +51 -0
- package/dist/core/lessons/audit.js +57 -0
- package/dist/core/lessons/audit.js.map +1 -0
- package/dist/core/lessons/compaction.d.ts +5 -0
- package/dist/core/lessons/compaction.js +58 -0
- package/dist/core/lessons/compaction.js.map +1 -0
- package/dist/core/lessons/config.d.ts +15 -0
- package/dist/core/lessons/config.js +31 -0
- package/dist/core/lessons/config.js.map +1 -0
- package/dist/core/lessons/existing.d.ts +4 -0
- package/dist/core/lessons/existing.js +22 -0
- package/dist/core/lessons/existing.js.map +1 -0
- package/dist/core/lessons/extract.d.ts +21 -0
- package/dist/core/lessons/extract.js +106 -0
- package/dist/core/lessons/extract.js.map +1 -0
- package/dist/core/lessons/frontmatter.d.ts +29 -0
- package/dist/core/lessons/frontmatter.js +87 -0
- package/dist/core/lessons/frontmatter.js.map +1 -0
- package/dist/core/lessons/graph.d.ts +256 -0
- package/dist/core/lessons/graph.js +86 -0
- package/dist/core/lessons/graph.js.map +1 -0
- package/dist/core/lessons/jev.d.ts +36 -0
- package/dist/core/lessons/jev.js +100 -0
- package/dist/core/lessons/jev.js.map +1 -0
- package/dist/core/lessons/layout.d.ts +2 -0
- package/dist/core/lessons/layout.js +56 -0
- package/dist/core/lessons/layout.js.map +1 -0
- package/dist/core/lessons/learn.d.ts +38 -0
- package/dist/core/lessons/learn.js +190 -0
- package/dist/core/lessons/learn.js.map +1 -0
- package/dist/core/lessons/llm.d.ts +42 -0
- package/dist/core/lessons/llm.js +106 -0
- package/dist/core/lessons/llm.js.map +1 -0
- package/dist/core/lessons/memo.d.ts +19 -0
- package/dist/core/lessons/memo.js +170 -0
- package/dist/core/lessons/memo.js.map +1 -0
- package/dist/core/lessons/merge.d.ts +39 -0
- package/dist/core/lessons/merge.js +79 -0
- package/dist/core/lessons/merge.js.map +1 -0
- package/dist/core/lessons/queue.d.ts +6 -0
- package/dist/core/lessons/queue.js +52 -0
- package/dist/core/lessons/queue.js.map +1 -0
- package/dist/core/lessons/recent.d.ts +21 -0
- package/dist/core/lessons/recent.js +111 -0
- package/dist/core/lessons/recent.js.map +1 -0
- package/dist/core/lessons/schema.d.ts +26 -0
- package/dist/core/lessons/schema.js +64 -0
- package/dist/core/lessons/schema.js.map +1 -0
- package/dist/core/lessons/select.d.ts +19 -0
- package/dist/core/lessons/select.js +55 -0
- package/dist/core/lessons/select.js.map +1 -0
- package/dist/core/lessons/sidecar.d.ts +23 -0
- package/dist/core/lessons/sidecar.js +171 -0
- package/dist/core/lessons/sidecar.js.map +1 -0
- package/dist/core/lessons/storage.d.ts +21 -0
- package/dist/core/lessons/storage.js +65 -0
- package/dist/core/lessons/storage.js.map +1 -0
- package/dist/core/lessons/usage.d.ts +31 -0
- package/dist/core/lessons/usage.js +91 -0
- package/dist/core/lessons/usage.js.map +1 -0
- package/dist/core/lessons/write.d.ts +18 -0
- package/dist/core/lessons/write.js +131 -0
- package/dist/core/lessons/write.js.map +1 -0
- package/dist/core/library/build.d.ts +22 -0
- package/dist/core/library/build.js +163 -0
- package/dist/core/library/build.js.map +1 -0
- package/dist/core/library/group.d.ts +14 -0
- package/dist/core/library/group.js +129 -0
- package/dist/core/library/group.js.map +1 -0
- package/dist/core/library/index-file.d.ts +7 -0
- package/dist/core/library/index-file.js +250 -0
- package/dist/core/library/index-file.js.map +1 -0
- package/dist/core/library/schema.d.ts +78 -0
- package/dist/core/library/schema.js +37 -0
- package/dist/core/library/schema.js.map +1 -0
- package/dist/core/library/skills.d.ts +26 -0
- package/dist/core/library/skills.js +168 -0
- package/dist/core/library/skills.js.map +1 -0
- package/dist/core/library/sync.d.ts +48 -0
- package/dist/core/library/sync.js +271 -0
- package/dist/core/library/sync.js.map +1 -0
- package/dist/core/lock.d.ts +6 -0
- package/dist/core/lock.js +56 -0
- package/dist/core/lock.js.map +1 -0
- package/dist/core/pull.d.ts +23 -0
- package/dist/core/pull.js +147 -0
- package/dist/core/pull.js.map +1 -0
- package/dist/core/push.d.ts +29 -0
- package/dist/core/push.js +170 -0
- package/dist/core/push.js.map +1 -0
- package/dist/core/remote-layout.d.ts +67 -0
- package/dist/core/remote-layout.js +94 -0
- package/dist/core/remote-layout.js.map +1 -0
- package/dist/core/sessions.d.ts +29 -0
- package/dist/core/sessions.js +96 -0
- package/dist/core/sessions.js.map +1 -0
- package/dist/core/state.d.ts +94 -0
- package/dist/core/state.js +62 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/sync.d.ts +48 -0
- package/dist/core/sync.js +140 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/core/time-window.d.ts +8 -0
- package/dist/core/time-window.js +36 -0
- package/dist/core/time-window.js.map +1 -0
- package/dist/daemon.d.ts +73 -0
- package/dist/daemon.js +272 -0
- package/dist/daemon.js.map +1 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +28 -0
- package/dist/format.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +9 -0
- package/dist/main.js.map +1 -0
- package/dist/paths.d.ts +19 -0
- package/dist/paths.js +19 -0
- package/dist/paths.js.map +1 -0
- package/dist/progress-console.d.ts +12 -0
- package/dist/progress-console.js +42 -0
- package/dist/progress-console.js.map +1 -0
- package/dist/progress.d.ts +7 -0
- package/dist/progress.js +6 -0
- package/dist/progress.js.map +1 -0
- package/dist/remote/create-remote.d.ts +20 -0
- package/dist/remote/create-remote.js +86 -0
- package/dist/remote/create-remote.js.map +1 -0
- package/dist/remote/prompt.d.ts +17 -0
- package/dist/remote/prompt.js +111 -0
- package/dist/remote/prompt.js.map +1 -0
- package/dist/remote/s3-sigv4.d.ts +28 -0
- package/dist/remote/s3-sigv4.js +100 -0
- package/dist/remote/s3-sigv4.js.map +1 -0
- package/dist/remote/s3-store.d.ts +75 -0
- package/dist/remote/s3-store.js +438 -0
- package/dist/remote/s3-store.js.map +1 -0
- package/dist/remote/s3-vendors.d.ts +31 -0
- package/dist/remote/s3-vendors.js +47 -0
- package/dist/remote/s3-vendors.js.map +1 -0
- package/dist/remote/store.d.ts +44 -0
- package/dist/remote/store.js +2 -0
- package/dist/remote/store.js.map +1 -0
- package/dist/remote/webdav-store.d.ts +41 -0
- package/dist/remote/webdav-store.js +285 -0
- package/dist/remote/webdav-store.js.map +1 -0
- package/dist/sqlite.d.ts +3 -0
- package/dist/sqlite.js +12 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/status.d.ts +82 -0
- package/dist/status.js +162 -0
- package/dist/status.js.map +1 -0
- package/package.json +43 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../src/core/library/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile, unlink } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
export function isProcessAlive(pid) {
|
|
5
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
6
|
+
return false;
|
|
7
|
+
try {
|
|
8
|
+
process.kill(pid, 0);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 跨进程互斥锁(daemon 与手动 sync 不能同时写同一份 vault/state)。
|
|
17
|
+
* 陈旧锁(进程已死)自动接管;释放时校验 token,避免误删别人的锁。
|
|
18
|
+
*/
|
|
19
|
+
export async function acquireLock(path) {
|
|
20
|
+
await mkdir(dirname(path), { recursive: true });
|
|
21
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
22
|
+
const token = randomUUID();
|
|
23
|
+
try {
|
|
24
|
+
const fh = await open(path, 'wx');
|
|
25
|
+
await fh.writeFile(JSON.stringify({ pid: process.pid, startedAt: Date.now(), token }));
|
|
26
|
+
await fh.close();
|
|
27
|
+
return async () => {
|
|
28
|
+
try {
|
|
29
|
+
const cur = JSON.parse(await readFile(path, 'utf8'));
|
|
30
|
+
if (cur.token === token)
|
|
31
|
+
await unlink(path);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// 锁已不存在
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (err.code !== 'EEXIST')
|
|
40
|
+
throw err;
|
|
41
|
+
let existing = null;
|
|
42
|
+
try {
|
|
43
|
+
existing = JSON.parse(await readFile(path, 'utf8'));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
existing = null;
|
|
47
|
+
}
|
|
48
|
+
if (existing && typeof existing.pid === 'number' && isProcessAlive(existing.pid)) {
|
|
49
|
+
throw new Error(`已有同步在运行 (pid ${existing.pid}),请等待其结束`);
|
|
50
|
+
}
|
|
51
|
+
await unlink(path).catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error('无法获取同步锁');
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/core/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAClC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YACvF,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,KAAK,IAAI,EAAE;gBAChB,IAAI,CAAC;oBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAuB,CAAC;oBAC3E,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK;wBAAE,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC9C,CAAC;gBAAC,MAAM,CAAC;oBACP,QAAQ;gBACV,CAAC;YACH,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;YAChE,IAAI,QAAQ,GAA4B,IAAI,CAAC;YAC7C,IAAI,CAAC;gBACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAqB,CAAC;YAC1E,CAAC;YAAC,MAAM,CAAC;gBACP,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;YACD,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,GAAG,KAAK,QAAQ,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjF,MAAM,IAAI,KAAK,CAAC,gBAAgB,QAAQ,CAAC,GAAG,UAAU,CAAC,CAAC;YAC1D,CAAC;YACD,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Progress } from '../progress.js';
|
|
2
|
+
import type { State } from './state.js';
|
|
3
|
+
import type { RemoteStore } from '../remote/store.js';
|
|
4
|
+
export interface PullOptions {
|
|
5
|
+
remote: RemoteStore;
|
|
6
|
+
deviceId: string;
|
|
7
|
+
vaultRoot: string;
|
|
8
|
+
state: State;
|
|
9
|
+
progress?: Progress;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export interface PullResult {
|
|
13
|
+
downloadedChunks: number;
|
|
14
|
+
downloadedBytes: number;
|
|
15
|
+
downloadedItems: number;
|
|
16
|
+
skippedItems: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 拉取其他设备的增量,重建到本地 vault(只增不删)。
|
|
20
|
+
* 索引里已包含块清单,无需逐会话取清单;单条记录先写临时文件,
|
|
21
|
+
* 全部块校验通过后才 rename,断电不会留下半个会话。
|
|
22
|
+
*/
|
|
23
|
+
export declare function pullRemote(opts: PullOptions): Promise<PullResult>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
3
|
+
import { mkdir, rename, stat, unlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
6
|
+
import { hashFile } from './chunker.js';
|
|
7
|
+
import { throwIfAborted } from './collect.js';
|
|
8
|
+
import { chunkRemotePath, indexPath, itemKey, objectRemotePath, validateIndexItem, } from './remote-layout.js';
|
|
9
|
+
import { silentProgress } from '../progress.js';
|
|
10
|
+
import { EVENTS_PREFIX } from './events/schema.js';
|
|
11
|
+
import { rawFilePath } from './layout.js';
|
|
12
|
+
const SAFE_DEVICE = /^[A-Za-z0-9._-]+$/;
|
|
13
|
+
const SAFE_HARNESS = /^[a-z0-9-]+$/;
|
|
14
|
+
/**
|
|
15
|
+
* 拉取其他设备的增量,重建到本地 vault(只增不删)。
|
|
16
|
+
* 索引里已包含块清单,无需逐会话取清单;单条记录先写临时文件,
|
|
17
|
+
* 全部块校验通过后才 rename,断电不会留下半个会话。
|
|
18
|
+
*/
|
|
19
|
+
export async function pullRemote(opts) {
|
|
20
|
+
const { remote, deviceId, vaultRoot, state } = opts;
|
|
21
|
+
const progress = opts.progress ?? silentProgress;
|
|
22
|
+
const devices = (await remote.listDevices()).filter((d) => SAFE_DEVICE.test(d) && d !== '.' && d !== '..');
|
|
23
|
+
progress.phase('pull', `${devices.length} 台设备`);
|
|
24
|
+
let downloadedChunks = 0;
|
|
25
|
+
let downloadedBytes = 0;
|
|
26
|
+
let downloadedItems = 0;
|
|
27
|
+
let skippedItems = 0;
|
|
28
|
+
let itemNo = 0;
|
|
29
|
+
for (const dev of devices) {
|
|
30
|
+
const rawIndex = await remote.readJson(indexPath(dev));
|
|
31
|
+
if (!rawIndex || typeof rawIndex !== 'object')
|
|
32
|
+
continue;
|
|
33
|
+
const index = rawIndex;
|
|
34
|
+
if (!index.items || typeof index.items !== 'object')
|
|
35
|
+
continue;
|
|
36
|
+
const pulled = (state.pulled[dev] ??= {});
|
|
37
|
+
const entries = Object.values(index.items);
|
|
38
|
+
for (const item of entries) {
|
|
39
|
+
throwIfAborted(opts.signal);
|
|
40
|
+
itemNo++;
|
|
41
|
+
if (!SAFE_HARNESS.test(item.harness)) {
|
|
42
|
+
throw new Error(`远端 harness 名非法: ${item.harness}`);
|
|
43
|
+
}
|
|
44
|
+
validateIndexItem(item);
|
|
45
|
+
// 旧版派生数据(对话层)不再放进 raw;trove 端已迁移到 library
|
|
46
|
+
if (item.relPath.startsWith(EVENTS_PREFIX)) {
|
|
47
|
+
skippedItems++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const stateKey = itemKey(item.harness, item.key);
|
|
51
|
+
if (pulled[stateKey]?.chunksHash === item.chunksHash) {
|
|
52
|
+
skippedItems++;
|
|
53
|
+
progress.item(`${dev}/${item.relPath}`, itemNo, entries.length, 0);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
// 本机设备且本地推送记录与索引一致:直接记账,省掉逐文件 stat
|
|
57
|
+
if (dev === deviceId) {
|
|
58
|
+
const pushed = state.pushed[item.harness]?.[item.relPath];
|
|
59
|
+
if (pushed && pushed.generation === item.generation && pushed.size === item.size) {
|
|
60
|
+
pulled[stateKey] = {
|
|
61
|
+
generation: item.generation,
|
|
62
|
+
size: item.size,
|
|
63
|
+
chunksHash: item.chunksHash,
|
|
64
|
+
};
|
|
65
|
+
skippedItems++;
|
|
66
|
+
progress.item(`${dev}/${item.relPath}`, itemNo, entries.length, 0);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const target = rawFilePath(vaultRoot, dev, item.harness, item.relPath);
|
|
71
|
+
let localSize = -1;
|
|
72
|
+
try {
|
|
73
|
+
localSize = (await stat(target)).size;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
localSize = -1;
|
|
77
|
+
}
|
|
78
|
+
// 本机设备:只要本地文件存在就视为权威,绝不回拉覆盖自己。
|
|
79
|
+
// (世代不一致 + 本地更小时,远端可能是更旧的快照;覆盖会永久丢失本地新内容。)
|
|
80
|
+
if (dev === deviceId && localSize >= 0) {
|
|
81
|
+
pulled[stateKey] = {
|
|
82
|
+
generation: item.generation,
|
|
83
|
+
size: item.size,
|
|
84
|
+
chunksHash: item.chunksHash,
|
|
85
|
+
};
|
|
86
|
+
skippedItems++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const sameGeneration = pulled[stateKey]?.generation === item.generation;
|
|
90
|
+
// 续传起点必须是清单里的真实块边界(清单可能来自不同块大小的旧版本)
|
|
91
|
+
let start = 0;
|
|
92
|
+
if (sameGeneration && localSize >= 0 && localSize <= item.size) {
|
|
93
|
+
const incomplete = item.chunks.find((c) => c.offset + c.length > localSize);
|
|
94
|
+
start = incomplete ? incomplete.offset : item.size;
|
|
95
|
+
}
|
|
96
|
+
const tmp = `${target}.tmp-${randomUUID()}`;
|
|
97
|
+
try {
|
|
98
|
+
await mkdir(dirname(target), { recursive: true });
|
|
99
|
+
if (start > 0) {
|
|
100
|
+
await pipeline(createReadStream(target, { start: 0, end: start - 1 }), createWriteStream(tmp));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
await writeFile(tmp, '');
|
|
104
|
+
}
|
|
105
|
+
for (const chunk of item.chunks) {
|
|
106
|
+
if (chunk.offset < start)
|
|
107
|
+
continue;
|
|
108
|
+
const tmpChunk = `${tmp}.chunk-${chunk.offset}`;
|
|
109
|
+
const remotePath = (item.layout ?? 'chunk') === 'object'
|
|
110
|
+
? objectRemotePath(dev, item.harness, item.relPath)
|
|
111
|
+
: chunkRemotePath(dev, item.harness, item.key, item.generation, chunk);
|
|
112
|
+
try {
|
|
113
|
+
await remote.readFile(remotePath, tmpChunk);
|
|
114
|
+
const got = await hashFile(tmpChunk);
|
|
115
|
+
if (got !== chunk.sha256) {
|
|
116
|
+
throw new Error(`远端块校验失败: ${item.relPath} @${chunk.offset}`);
|
|
117
|
+
}
|
|
118
|
+
await pipeline(createReadStream(tmpChunk), createWriteStream(tmp, { flags: 'a' }));
|
|
119
|
+
downloadedChunks++;
|
|
120
|
+
downloadedBytes += chunk.length;
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
await unlink(tmpChunk).catch(() => { });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const finalSize = (await stat(tmp)).size;
|
|
127
|
+
if (finalSize !== item.size) {
|
|
128
|
+
throw new Error(`拉取后大小不一致: ${item.relPath} 期望 ${item.size} 实得 ${finalSize}`);
|
|
129
|
+
}
|
|
130
|
+
await rename(tmp, target);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
await unlink(tmp).catch(() => { });
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
pulled[stateKey] = {
|
|
137
|
+
generation: item.generation,
|
|
138
|
+
size: item.size,
|
|
139
|
+
chunksHash: item.chunksHash,
|
|
140
|
+
};
|
|
141
|
+
downloadedItems++;
|
|
142
|
+
progress.item(`${dev}/${item.relPath}`, itemNo, entries.length, item.size);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { downloadedChunks, downloadedBytes, downloadedItems, skippedItems };
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=pull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.js","sourceRoot":"","sources":["../../src/core/pull.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { State } from './state.js';
|
|
2
|
+
import { type Progress } from '../progress.js';
|
|
3
|
+
import type { RemoteStore } from '../remote/store.js';
|
|
4
|
+
export interface PushOptions {
|
|
5
|
+
remote: RemoteStore;
|
|
6
|
+
deviceId: string;
|
|
7
|
+
vaultRoot: string;
|
|
8
|
+
state: State;
|
|
9
|
+
/** 当前远端的身份指纹;用于判断本地推送记录是否还作数(换后端时作废)。 */
|
|
10
|
+
remoteId?: string;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
chunkSize?: number;
|
|
13
|
+
concurrency?: number;
|
|
14
|
+
progress?: Progress;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}
|
|
17
|
+
export interface PushResult {
|
|
18
|
+
uploadedChunks: number;
|
|
19
|
+
uploadedBytes: number;
|
|
20
|
+
uploadedItems: number;
|
|
21
|
+
skippedItems: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 把本地 vault 中本设备的归档增量推到远端(块级)。
|
|
25
|
+
*
|
|
26
|
+
* 远端只有"块 + 单份索引":块先传,索引最后写(提交点)。
|
|
27
|
+
* 重复运行只补缺失的块;世代变化时旧世代块保留(永不删除)。
|
|
28
|
+
*/
|
|
29
|
+
export declare function pushLocal(opts: PushOptions): Promise<PushResult>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { CHUNK_SIZE, chunkRanges, hashFile, hashRange, reusableChunks } from './chunker.js';
|
|
3
|
+
import { mapLimit } from './concurrency.js';
|
|
4
|
+
import { throwIfAborted } from './collect.js';
|
|
5
|
+
import { chunkRemotePath, chunksHash, encodeKey, indexPath, itemKey, objectRemotePath, validateRelPath, } from './remote-layout.js';
|
|
6
|
+
import { silentProgress } from '../progress.js';
|
|
7
|
+
import { rawFilePath } from './layout.js';
|
|
8
|
+
/** 索引检查点间隔(ms):长同步被打断最多损失一分钟的续传记录。 */
|
|
9
|
+
const INDEX_CHECKPOINT_MS = 60_000;
|
|
10
|
+
/**
|
|
11
|
+
* 把本地 vault 中本设备的归档增量推到远端(块级)。
|
|
12
|
+
*
|
|
13
|
+
* 远端只有"块 + 单份索引":块先传,索引最后写(提交点)。
|
|
14
|
+
* 重复运行只补缺失的块;世代变化时旧世代块保留(永不删除)。
|
|
15
|
+
*/
|
|
16
|
+
export async function pushLocal(opts) {
|
|
17
|
+
const { remote, deviceId, vaultRoot, state } = opts;
|
|
18
|
+
const now = opts.now ?? Date.now;
|
|
19
|
+
const chunkSize = opts.chunkSize ?? CHUNK_SIZE;
|
|
20
|
+
// 并发数由后端给建议:WebDAV 实测并发 PUT 会 ECONNRESET(给 1),
|
|
21
|
+
// 对象存储允许并发(给更高值),这里显式传参时以传参为准。
|
|
22
|
+
const concurrency = opts.concurrency ?? remote.concurrencyHint?.() ?? 1;
|
|
23
|
+
// 对象存储一个文件一个对象(可读);WebDAV 保持应用层分块(服务端无 multipart)
|
|
24
|
+
const layout = remote.layoutHint?.() ?? 'chunk';
|
|
25
|
+
const progress = opts.progress ?? silentProgress;
|
|
26
|
+
const previous = (await remote.readJson(indexPath(deviceId)));
|
|
27
|
+
const index = {
|
|
28
|
+
v: 1,
|
|
29
|
+
deviceId,
|
|
30
|
+
updatedAt: previous?.updatedAt ?? 0,
|
|
31
|
+
items: { ...(previous?.items ?? {}) },
|
|
32
|
+
};
|
|
33
|
+
const tasks = [];
|
|
34
|
+
for (const [harness, bucket] of Object.entries(state.sources)) {
|
|
35
|
+
for (const [relPath, entry] of Object.entries(bucket)) {
|
|
36
|
+
if (!validateRelPath(relPath))
|
|
37
|
+
throw new Error(`非法路径(源状态): ${relPath}`);
|
|
38
|
+
const absPath = rawFilePath(vaultRoot, deviceId, harness, relPath);
|
|
39
|
+
try {
|
|
40
|
+
if (!(await stat(absPath)).isFile())
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.code === 'ENOENT')
|
|
45
|
+
continue;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
tasks.push({ harness, relPath, absPath, entry });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
progress.phase('push', `${tasks.length} 个文件`);
|
|
52
|
+
let uploadedChunks = 0;
|
|
53
|
+
let uploadedBytes = 0;
|
|
54
|
+
let uploadedItems = 0;
|
|
55
|
+
let skippedItems = 0;
|
|
56
|
+
let indexChanged = false;
|
|
57
|
+
let lastCheckpoint = Date.now();
|
|
58
|
+
const commitIndex = async () => {
|
|
59
|
+
index.updatedAt = now();
|
|
60
|
+
await remote.writeJson(indexPath(deviceId), index);
|
|
61
|
+
indexChanged = false;
|
|
62
|
+
lastCheckpoint = Date.now();
|
|
63
|
+
};
|
|
64
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
65
|
+
throwIfAborted(opts.signal);
|
|
66
|
+
const { harness, relPath, absPath, entry } = tasks[i];
|
|
67
|
+
const key = encodeKey(relPath);
|
|
68
|
+
const ik = itemKey(harness, key);
|
|
69
|
+
const st = await stat(absPath);
|
|
70
|
+
const pushed = state.pushed[harness]?.[relPath];
|
|
71
|
+
const inIndex = index.items[ik];
|
|
72
|
+
// 索引是远端权威:世代 + 大小 + 布局一致即内容一致(append-only,改写会换世代)。
|
|
73
|
+
// 即使本地 state 丢失,也能零重传地续上。布局变化(如 S3 改单对象)会触发重推。
|
|
74
|
+
if (inIndex &&
|
|
75
|
+
inIndex.generation === entry.generation &&
|
|
76
|
+
inIndex.size === st.size &&
|
|
77
|
+
(inIndex.layout ?? 'chunk') === layout) {
|
|
78
|
+
(state.pushed[harness] ??= {})[relPath] ??= {
|
|
79
|
+
generation: inIndex.generation,
|
|
80
|
+
size: inIndex.size,
|
|
81
|
+
chunks: inIndex.chunks,
|
|
82
|
+
chunksHash: inIndex.chunksHash,
|
|
83
|
+
};
|
|
84
|
+
skippedItems++;
|
|
85
|
+
progress.item(relPath, i + 1, tasks.length, 0);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const chunks = [];
|
|
89
|
+
if (layout === 'object') {
|
|
90
|
+
// 整文件一个对象;分片交给 S3 Multipart,块清单退化为单块
|
|
91
|
+
if (st.size > 0) {
|
|
92
|
+
chunks.push({ offset: 0, length: st.size, sha256: await hashFile(absPath) });
|
|
93
|
+
}
|
|
94
|
+
await remote.writeFile(objectRemotePath(deviceId, harness, relPath), absPath);
|
|
95
|
+
uploadedChunks += chunks.length;
|
|
96
|
+
uploadedBytes += st.size;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const ranges = chunkRanges(st.size, chunkSize);
|
|
100
|
+
// 可复用块的来源:
|
|
101
|
+
// - 远端索引:远端自己说有这些块(最权威),且布局必须是 chunk
|
|
102
|
+
// - 本地 state.pushed:只在"同一个远端"时可信(索引丢了但块还在,只补半块)
|
|
103
|
+
// 换后端后本地记录属于旧远端,不能复用 —— 否则新远端只剩索引没有数据。
|
|
104
|
+
const sameRemote = !opts.remoteId || !state.remoteId || opts.remoteId === state.remoteId;
|
|
105
|
+
const reuse = inIndex &&
|
|
106
|
+
inIndex.generation === entry.generation &&
|
|
107
|
+
(inIndex.layout ?? 'chunk') === 'chunk'
|
|
108
|
+
? reusableChunks(inIndex.chunks, inIndex.size, chunkSize)
|
|
109
|
+
: pushed && sameRemote && pushed.generation === entry.generation
|
|
110
|
+
? reusableChunks(pushed.chunks, pushed.size, chunkSize)
|
|
111
|
+
: new Map();
|
|
112
|
+
const toUpload = [];
|
|
113
|
+
for (const range of ranges) {
|
|
114
|
+
const old = reuse.get(range.offset);
|
|
115
|
+
if (old) {
|
|
116
|
+
chunks.push(old);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const chunk = {
|
|
120
|
+
...range,
|
|
121
|
+
sha256: await hashRange(absPath, range.offset, range.length),
|
|
122
|
+
};
|
|
123
|
+
chunks.push(chunk);
|
|
124
|
+
toUpload.push({
|
|
125
|
+
chunk,
|
|
126
|
+
remotePath: chunkRemotePath(deviceId, harness, key, entry.generation, chunk),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
await mapLimit(toUpload, concurrency, async ({ chunk, remotePath }) => {
|
|
130
|
+
await remote.writeFile(remotePath, absPath, {
|
|
131
|
+
offset: chunk.offset,
|
|
132
|
+
length: chunk.length,
|
|
133
|
+
});
|
|
134
|
+
uploadedChunks++;
|
|
135
|
+
uploadedBytes += chunk.length;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const hash = chunksHash(chunks);
|
|
139
|
+
(state.pushed[harness] ??= {})[relPath] = {
|
|
140
|
+
generation: entry.generation,
|
|
141
|
+
size: st.size,
|
|
142
|
+
chunks,
|
|
143
|
+
chunksHash: hash,
|
|
144
|
+
};
|
|
145
|
+
index.items[ik] = {
|
|
146
|
+
harness,
|
|
147
|
+
key,
|
|
148
|
+
relPath,
|
|
149
|
+
sessionKey: entry.sessionKey,
|
|
150
|
+
generation: entry.generation,
|
|
151
|
+
size: st.size,
|
|
152
|
+
chunks,
|
|
153
|
+
chunksHash: hash,
|
|
154
|
+
layout,
|
|
155
|
+
updatedAt: now(),
|
|
156
|
+
};
|
|
157
|
+
indexChanged = true;
|
|
158
|
+
uploadedItems++;
|
|
159
|
+
progress.item(relPath, i + 1, tasks.length, st.size);
|
|
160
|
+
if (Date.now() - lastCheckpoint >= INDEX_CHECKPOINT_MS)
|
|
161
|
+
await commitIndex();
|
|
162
|
+
}
|
|
163
|
+
if (indexChanged)
|
|
164
|
+
await commitIndex();
|
|
165
|
+
// 记住这次推送属于哪个远端:下次换后端时,本地的复用记录会自动作废
|
|
166
|
+
if (opts.remoteId)
|
|
167
|
+
state.remoteId = opts.remoteId;
|
|
168
|
+
return { uploadedChunks, uploadedBytes, uploadedItems, skippedItems };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/core/push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ChunkRef } from './chunker.js';
|
|
2
|
+
/**
|
|
3
|
+
* 远端布局(每设备自描述,对象数尽量少):
|
|
4
|
+
*
|
|
5
|
+
* <device>/index.json 设备索引(清单 + 块哈希都在这里)
|
|
6
|
+
* <device>/meta.json 设备名 / lastSeen
|
|
7
|
+
* <device>/chunks/<harness>/<key>.<gen>.<off>-<len>.bin 不可变块(WebDAV)
|
|
8
|
+
* <device>/files/<harness>/<relPath> 整文件对象(S3,可读)
|
|
9
|
+
*
|
|
10
|
+
* WebDAV 没有原生分片上传,按块存;对象存储一个文件一个对象,路径即原相对路径。
|
|
11
|
+
* 两种布局由索引项的 layout 字段区分,缺省 'chunk'(兼容老索引)。
|
|
12
|
+
*
|
|
13
|
+
* 每个 harness 只有一个目录,避免"每会话多级 MKCOL"的数千次往返;
|
|
14
|
+
* 索引是单次提交点,块先传、索引后写。
|
|
15
|
+
* key = base64url(relPath),避免中文、空格、'..' 进入远端路径。
|
|
16
|
+
*/
|
|
17
|
+
export interface IndexItem {
|
|
18
|
+
harness: string;
|
|
19
|
+
key: string;
|
|
20
|
+
relPath: string;
|
|
21
|
+
sessionKey: string;
|
|
22
|
+
generation: string;
|
|
23
|
+
size: number;
|
|
24
|
+
chunks: ChunkRef[];
|
|
25
|
+
/** chunks 数组的 sha256,拉取时判断是否需要取索引里的块列表 */
|
|
26
|
+
chunksHash: string;
|
|
27
|
+
/** 缺省 'chunk':老索引没有这个字段,按分块布局解释。 */
|
|
28
|
+
layout?: 'chunk' | 'object';
|
|
29
|
+
updatedAt: number;
|
|
30
|
+
}
|
|
31
|
+
export interface DeviceIndex {
|
|
32
|
+
v: 1;
|
|
33
|
+
deviceId: string;
|
|
34
|
+
updatedAt: number;
|
|
35
|
+
items: Record<string, IndexItem>;
|
|
36
|
+
}
|
|
37
|
+
export interface DeviceMeta {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
lastSeen: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function chunksHash(chunks: readonly ChunkRef[]): string;
|
|
43
|
+
export declare function encodeKey(relPath: string): string;
|
|
44
|
+
/** 解码并校验远端不可信输入,拒绝绝对路径/穿越/反斜杠/NUL。 */
|
|
45
|
+
export declare function decodeKey(key: string): string;
|
|
46
|
+
export declare function validateRelPath(relPath: string): boolean;
|
|
47
|
+
export declare function itemKey(harness: string, key: string): string;
|
|
48
|
+
/** 设备在远端的根目录:raw/<device>;library 是另一棵独立的树。 */
|
|
49
|
+
export declare function deviceRemoteRoot(deviceId: string): string;
|
|
50
|
+
export declare function indexPath(deviceId: string): string;
|
|
51
|
+
export declare function metaPath(deviceId: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* 扁平块路径:同一 harness 共用一个目录,文件名自带 key/世代/区间。
|
|
54
|
+
* 超长 key(深层项目路径)改用 key+generation 的哈希前缀,保证文件名 <= 240。
|
|
55
|
+
*/
|
|
56
|
+
export declare function chunkRemotePath(deviceId: string, harness: string, key: string, generation: string, chunk: {
|
|
57
|
+
offset: number;
|
|
58
|
+
length: number;
|
|
59
|
+
}): string;
|
|
60
|
+
/**
|
|
61
|
+
* 单对象布局:与本地 vault 同构,控制台里能直接浏览/下载会话原文。
|
|
62
|
+
* 路径即 relPath,同路径覆盖写(不加世代后缀)。
|
|
63
|
+
* 超长路径超过 240 字符时整体换成 relPath 的哈希,保证 S3 key 不超限。
|
|
64
|
+
*/
|
|
65
|
+
export declare function objectRemotePath(deviceId: string, harness: string, relPath: string): string;
|
|
66
|
+
/** 校验索引项(远端不可信):块必须从 0 连续铺满整个文件。 */
|
|
67
|
+
export declare function validateIndexItem(item: IndexItem): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export function chunksHash(chunks) {
|
|
3
|
+
return createHash('sha256').update(JSON.stringify(chunks)).digest('hex');
|
|
4
|
+
}
|
|
5
|
+
export function encodeKey(relPath) {
|
|
6
|
+
return Buffer.from(relPath, 'utf8').toString('base64url');
|
|
7
|
+
}
|
|
8
|
+
/** 解码并校验远端不可信输入,拒绝绝对路径/穿越/反斜杠/NUL。 */
|
|
9
|
+
export function decodeKey(key) {
|
|
10
|
+
let rel;
|
|
11
|
+
try {
|
|
12
|
+
rel = Buffer.from(key, 'base64url').toString('utf8');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error(`非法 path key: ${key}`);
|
|
16
|
+
}
|
|
17
|
+
if (!validateRelPath(rel))
|
|
18
|
+
throw new Error(`非法路径(远端不可信): ${rel}`);
|
|
19
|
+
return rel;
|
|
20
|
+
}
|
|
21
|
+
export function validateRelPath(relPath) {
|
|
22
|
+
if (!relPath || relPath.startsWith('/') || relPath.includes('\\') || relPath.includes('\0')) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const parts = relPath.split('/');
|
|
26
|
+
return parts.every((p) => p !== '' && p !== '.' && p !== '..');
|
|
27
|
+
}
|
|
28
|
+
export function itemKey(harness, key) {
|
|
29
|
+
return `${harness}:${key}`;
|
|
30
|
+
}
|
|
31
|
+
/** 设备在远端的根目录:raw/<device>;library 是另一棵独立的树。 */
|
|
32
|
+
export function deviceRemoteRoot(deviceId) {
|
|
33
|
+
return `raw/${deviceId}`;
|
|
34
|
+
}
|
|
35
|
+
export function indexPath(deviceId) {
|
|
36
|
+
return `${deviceRemoteRoot(deviceId)}/index.json`;
|
|
37
|
+
}
|
|
38
|
+
export function metaPath(deviceId) {
|
|
39
|
+
return `${deviceRemoteRoot(deviceId)}/meta.json`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 扁平块路径:同一 harness 共用一个目录,文件名自带 key/世代/区间。
|
|
43
|
+
* 超长 key(深层项目路径)改用 key+generation 的哈希前缀,保证文件名 <= 240。
|
|
44
|
+
*/
|
|
45
|
+
export function chunkRemotePath(deviceId, harness, key, generation, chunk) {
|
|
46
|
+
const suffix = `${String(chunk.offset).padStart(12, '0')}-${chunk.length}.bin`;
|
|
47
|
+
const plain = `${key}.${generation}.${suffix}`;
|
|
48
|
+
const name = plain.length <= 240
|
|
49
|
+
? plain
|
|
50
|
+
: `${createHash('sha256').update(`${key}:${generation}`).digest('hex').slice(0, 32)}.${suffix}`;
|
|
51
|
+
return `${deviceRemoteRoot(deviceId)}/chunks/${harness}/${name}`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 单对象布局:与本地 vault 同构,控制台里能直接浏览/下载会话原文。
|
|
55
|
+
* 路径即 relPath,同路径覆盖写(不加世代后缀)。
|
|
56
|
+
* 超长路径超过 240 字符时整体换成 relPath 的哈希,保证 S3 key 不超限。
|
|
57
|
+
*/
|
|
58
|
+
export function objectRemotePath(deviceId, harness, relPath) {
|
|
59
|
+
const plain = `${deviceRemoteRoot(deviceId)}/files/${harness}/${relPath}`;
|
|
60
|
+
const name = plain.length <= 240
|
|
61
|
+
? relPath
|
|
62
|
+
: createHash('sha256').update(relPath).digest('hex').slice(0, 32);
|
|
63
|
+
return `${deviceRemoteRoot(deviceId)}/files/${harness}/${name}`;
|
|
64
|
+
}
|
|
65
|
+
/** 校验索引项(远端不可信):块必须从 0 连续铺满整个文件。 */
|
|
66
|
+
export function validateIndexItem(item) {
|
|
67
|
+
if (!validateRelPath(item.relPath))
|
|
68
|
+
throw new Error(`非法路径(远端不可信): ${item.relPath}`);
|
|
69
|
+
if (decodeKey(item.key) !== item.relPath)
|
|
70
|
+
throw new Error(`远端索引路径不一致: ${item.relPath}`);
|
|
71
|
+
if (!item.generation || item.generation.includes('/') || item.generation.includes('.')) {
|
|
72
|
+
throw new Error(`远端世代名非法: ${item.generation}`);
|
|
73
|
+
}
|
|
74
|
+
if (item.layout !== undefined && item.layout !== 'chunk' && item.layout !== 'object') {
|
|
75
|
+
throw new Error(`远端布局非法: ${item.relPath}`);
|
|
76
|
+
}
|
|
77
|
+
let offset = 0;
|
|
78
|
+
for (const c of item.chunks) {
|
|
79
|
+
if (c.offset !== offset || c.length <= 0 || !/^[0-9a-f]{64}$/.test(c.sha256)) {
|
|
80
|
+
throw new Error(`远端块布局非法: ${item.relPath}`);
|
|
81
|
+
}
|
|
82
|
+
offset += c.length;
|
|
83
|
+
}
|
|
84
|
+
if (offset !== item.size)
|
|
85
|
+
throw new Error(`远端块未铺满文件: ${item.relPath}`);
|
|
86
|
+
// 单对象布局必须是一整块;空文件沿用零块(远端可能没有对象)
|
|
87
|
+
if (item.layout === 'object' && item.size > 0 && item.chunks.length !== 1) {
|
|
88
|
+
throw new Error(`远端单对象布局非法: ${item.relPath}`);
|
|
89
|
+
}
|
|
90
|
+
if (chunksHash(item.chunks) !== item.chunksHash) {
|
|
91
|
+
throw new Error(`远端块哈希不一致: ${item.relPath}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=remote-layout.js.map
|