推进 OPFS 机器会话快照加载入口
This commit is contained in:
63
text10.txt
63
text10.txt
@@ -415,3 +415,66 @@ host_wasm_opfs_browser_smokes=ok
|
|||||||
后校验 `payload.machineId` 与机器文件 path 结构,并补 Node/browser smoke;如果该入口
|
后校验 `payload.machineId` 与机器文件 path 结构,并补 Node/browser smoke;如果该入口
|
||||||
已足够清晰,再把 save/load machine session snapshot 接到 INI panel 的实际会话保存/恢复
|
已足够清晰,再把 save/load machine session snapshot 接到 INI panel 的实际会话保存/恢复
|
||||||
workflow。仍只处理 OPFS/filesystem/browser boundary,不引入或重写任何 CNC 语义。
|
workflow。仍只处理 OPFS/filesystem/browser boundary,不引入或重写任何 CNC 语义。
|
||||||
|
|
||||||
|
四、2026-06-14 继续执行记录:OPFS machine session snapshot load helper
|
||||||
|
|
||||||
|
本轮继续按“加快实质性推进铁律”推进 OPFS/session persistence 的实际 API 入口,
|
||||||
|
补齐 machine session snapshot 的读取校验侧能力。
|
||||||
|
|
||||||
|
完成内容:
|
||||||
|
|
||||||
|
- 在 `wasm-port/runtime/opfs/snapshot-store.js` 新增
|
||||||
|
`loadMachineSessionSnapshot(sessionId, machineId, options)`;
|
||||||
|
- 该 helper 复用既有 `loadSessionSnapshot(...)` envelope 校验,然后校验
|
||||||
|
`payload.machineId` 与调用方传入的 `machineId` 一致;
|
||||||
|
- 新增内部 machine snapshot payload 校验,要求 `payload.files` 为 plain object,
|
||||||
|
且 `ini`、`parameters`、`toolTable` 为合法 OPFS path,`gcode` 如存在也必须是合法
|
||||||
|
OPFS path;
|
||||||
|
- 校验只覆盖 host-side snapshot/path boundary,不解释 G-code、parameter、tool-table
|
||||||
|
内容,不扩大 CNC 语义边界;
|
||||||
|
- 在 Node OPFS smoke 中覆盖 machine snapshot load round trip、machine id mismatch
|
||||||
|
reject,以及非法 OPFS path reject;
|
||||||
|
- 在 browser INI panel smoke 中覆盖浏览器运行时的 checked machine snapshot load 和
|
||||||
|
wrong-machine reject;
|
||||||
|
- 未改变 generic session snapshot envelope、path model、file-service、save/load API
|
||||||
|
兼容性或已有 storage 行为;
|
||||||
|
- 未改变 OPFS bridge behavior、SDK planner、interpreter、INI、tool、parameter 或
|
||||||
|
LinuxCNC-owned runtime semantics。
|
||||||
|
|
||||||
|
验证已通过:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git diff --check
|
||||||
|
wasm-port/tests/opfs/node/verify_file_service.sh
|
||||||
|
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
|
||||||
|
wasm-port/tools/verify_vendor_sync.sh
|
||||||
|
wasm-port/tools/verify_no_standalone_cnc_semantics.sh
|
||||||
|
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh
|
||||||
|
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
|
||||||
|
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_interp_browser.sh
|
||||||
|
wasm-port/tests/host/verify_host_smokes.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
关键输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
opfs_file_service_node_smoke=ok
|
||||||
|
browser_ini_opfs_smoke=ok
|
||||||
|
vendor sync up to date
|
||||||
|
standalone CNC semantics guard complete
|
||||||
|
interp_wasm_node_smoke=ok
|
||||||
|
sim_configs_wasm_node_inventory_executed=28
|
||||||
|
sim_configs_wasm_node_inventory_passed=28
|
||||||
|
sim_configs_wasm_node_inventory_skipped=131
|
||||||
|
sim_configs_wasm_node_inventory_unexpected_fail=0
|
||||||
|
browser_interp_smoke=ok
|
||||||
|
host_wasm_opfs_browser_smokes=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
下一步建议:
|
||||||
|
|
||||||
|
继续把 machine session snapshot save/load helper 接到更明确的用户流程。优先扫描
|
||||||
|
`wasm-port/runtime/ui/ini-panel/app.js` 和 browser smoke,增加一个最小会话保存/恢复
|
||||||
|
workflow:保存当前 INI、parameter/tool-table path 和可选 G-code snapshot,再通过
|
||||||
|
`loadMachineSessionSnapshot(...)` 恢复出待加载文件清单。范围仍限定 OPFS/filesystem/UI
|
||||||
|
workflow,不解析或重写任何 LinuxCNC-owned CNC 语义。
|
||||||
|
|||||||
@@ -27,6 +27,27 @@ function optionalOpfsPath(value, label) {
|
|||||||
return normalizeOpfsPath(value);
|
return normalizeOpfsPath(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requiredSnapshotFilePath(files, key, label) {
|
||||||
|
const value = files[key];
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
throw new Error(`${label} must be a string.`);
|
||||||
|
}
|
||||||
|
return normalizeOpfsPath(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateMachineSessionSnapshotPayload(payload, machineId) {
|
||||||
|
assertPlainObject(payload, "machine session snapshot payload");
|
||||||
|
if (payload.machineId !== machineId) {
|
||||||
|
throw new Error(`Machine session snapshot id mismatch: ${payload.machineId}`);
|
||||||
|
}
|
||||||
|
assertPlainObject(payload.files, "machine session snapshot files");
|
||||||
|
requiredSnapshotFilePath(payload.files, "ini", "machine session INI OPFS path");
|
||||||
|
requiredSnapshotFilePath(payload.files, "parameters", "machine session parameter OPFS path");
|
||||||
|
requiredSnapshotFilePath(payload.files, "toolTable", "machine session tool table OPFS path");
|
||||||
|
optionalOpfsPath(payload.files.gcode, "machine session G-code OPFS path");
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
export function createMachineSessionSnapshotPayload(machineId, options = {}) {
|
export function createMachineSessionSnapshotPayload(machineId, options = {}) {
|
||||||
assertPlainObject(options, "machine session snapshot options");
|
assertPlainObject(options, "machine session snapshot options");
|
||||||
const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ??
|
const ini = optionalOpfsPath(options.iniOpfsPath, "INI OPFS path") ??
|
||||||
@@ -95,6 +116,12 @@ export async function saveMachineSessionSnapshot(sessionId, machineId, options =
|
|||||||
return saveSessionSnapshot(sessionId, payload, options);
|
return saveSessionSnapshot(sessionId, payload, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadMachineSessionSnapshot(sessionId, machineId, options = {}) {
|
||||||
|
const snapshot = await loadSessionSnapshot(sessionId, options);
|
||||||
|
validateMachineSessionSnapshotPayload(snapshot.payload, machineId);
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
export async function loadSessionSnapshot(sessionId, options = {}) {
|
export async function loadSessionSnapshot(sessionId, options = {}) {
|
||||||
const path = sessionSnapshotPath(sessionId, options.filename);
|
const path = sessionSnapshotPath(sessionId, options.filename);
|
||||||
const text = await loadTextFile(path, options.storage);
|
const text = await loadTextFile(path, options.storage);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
} from "../../runtime/opfs/path-model.js";
|
} from "../../runtime/opfs/path-model.js";
|
||||||
import {
|
import {
|
||||||
createMachineSessionSnapshotPayload,
|
createMachineSessionSnapshotPayload,
|
||||||
|
loadMachineSessionSnapshot,
|
||||||
loadSessionSnapshot,
|
loadSessionSnapshot,
|
||||||
saveMachineSessionSnapshot,
|
saveMachineSessionSnapshot,
|
||||||
saveSessionSnapshot,
|
saveSessionSnapshot,
|
||||||
@@ -221,6 +222,11 @@ TOOL_TABLE = browser-tool.tbl
|
|||||||
"browser-machine-session",
|
"browser-machine-session",
|
||||||
{ filename: "browser-machine-snapshot.json" },
|
{ filename: "browser-machine-snapshot.json" },
|
||||||
);
|
);
|
||||||
|
const loadedCheckedMachineSessionSnapshot = await loadMachineSessionSnapshot(
|
||||||
|
"browser-machine-session",
|
||||||
|
"browser-smoke",
|
||||||
|
{ filename: "browser-machine-snapshot.json" },
|
||||||
|
);
|
||||||
assertEqual(
|
assertEqual(
|
||||||
machineSessionSnapshot.payload.files.parameters,
|
machineSessionSnapshot.payload.files.parameters,
|
||||||
"linuxcnc/machines/browser-smoke/browser-linuxcnc.var",
|
"linuxcnc/machines/browser-smoke/browser-linuxcnc.var",
|
||||||
@@ -241,6 +247,20 @@ TOOL_TABLE = browser-tool.tbl
|
|||||||
"browser-machine-session",
|
"browser-machine-session",
|
||||||
"machine session snapshot metadata",
|
"machine session snapshot metadata",
|
||||||
);
|
);
|
||||||
|
assertEqual(
|
||||||
|
loadedCheckedMachineSessionSnapshot.payload.machineId,
|
||||||
|
"browser-smoke",
|
||||||
|
"checked machine session snapshot id",
|
||||||
|
);
|
||||||
|
await assertRejects(
|
||||||
|
"browser machine snapshot wrong machine",
|
||||||
|
() => loadMachineSessionSnapshot(
|
||||||
|
"browser-machine-session",
|
||||||
|
"other-browser-machine",
|
||||||
|
{ filename: "browser-machine-snapshot.json" },
|
||||||
|
),
|
||||||
|
/Machine session snapshot id mismatch/,
|
||||||
|
);
|
||||||
await assertRejects(
|
await assertRejects(
|
||||||
"browser snapshot invalid filename save",
|
"browser snapshot invalid filename save",
|
||||||
() => saveSessionSnapshot("browser-session", {}, {
|
() => saveSessionSnapshot("browser-session", {}, {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
createMachineSessionSnapshotPayload,
|
createMachineSessionSnapshotPayload,
|
||||||
createSessionSnapshot,
|
createSessionSnapshot,
|
||||||
|
loadMachineSessionSnapshot,
|
||||||
loadSessionSnapshot,
|
loadSessionSnapshot,
|
||||||
saveMachineSessionSnapshot,
|
saveMachineSessionSnapshot,
|
||||||
saveSessionSnapshot,
|
saveSessionSnapshot,
|
||||||
@@ -350,6 +351,40 @@ assert.deepEqual(
|
|||||||
}),
|
}),
|
||||||
machineSessionSnapshot,
|
machineSessionSnapshot,
|
||||||
);
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
await loadMachineSessionSnapshot("machine-session-1", "xyzab-tdr", {
|
||||||
|
storage,
|
||||||
|
filename: "machine-session-snapshot.json",
|
||||||
|
}),
|
||||||
|
machineSessionSnapshot,
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => loadMachineSessionSnapshot("machine-session-1", "other-machine", {
|
||||||
|
storage,
|
||||||
|
filename: "machine-session-snapshot.json",
|
||||||
|
}),
|
||||||
|
/Machine session snapshot id mismatch/,
|
||||||
|
);
|
||||||
|
await saveTextFile(
|
||||||
|
sessionSnapshotPath("machine-session-bad-files"),
|
||||||
|
JSON.stringify({
|
||||||
|
...machineSessionSnapshot,
|
||||||
|
sessionId: "machine-session-bad-files",
|
||||||
|
payload: {
|
||||||
|
machineId: "xyzab-tdr",
|
||||||
|
files: {
|
||||||
|
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
|
||||||
|
parameters: "../linuxcnc.var",
|
||||||
|
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
storage,
|
||||||
|
);
|
||||||
|
await assert.rejects(
|
||||||
|
() => loadMachineSessionSnapshot("machine-session-bad-files", "xyzab-tdr", { storage }),
|
||||||
|
/Invalid OPFS path/,
|
||||||
|
);
|
||||||
|
|
||||||
await saveMachineTextFiles("xyzab-tdr", {
|
await saveMachineTextFiles("xyzab-tdr", {
|
||||||
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
|
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
|
||||||
|
|||||||
Reference in New Issue
Block a user