From d40a6422e8a15239c73e1889ba2179736b5f04ff Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sun, 14 Jun 2026 15:57:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A8=E8=BF=9B=20OPFS=20G-code=20=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E8=B7=AF=E5=BE=84=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- text10.txt | 61 +++++++++++++++++++ wasm-port/runtime/opfs/machine-file-store.js | 15 +++++ wasm-port/runtime/ui/ini-panel/app.js | 28 +++++---- wasm-port/tests/browser/ini_panel_smoke.html | 11 ++++ .../tests/opfs/node/verify_file_service.mjs | 13 ++++ 5 files changed, 115 insertions(+), 13 deletions(-) diff --git a/text10.txt b/text10.txt index 84b5a77..3ad6363 100644 --- a/text10.txt +++ b/text10.txt @@ -732,3 +732,64 @@ host_wasm_opfs_browser_smokes=ok `linuxcnc/gcode/` 到 filename 的转换下沉为 OPFS G-code store 的可复用 helper, 例如 `gcodeFilenameFromProgramPath(...)`,并在 Node OPFS smoke 与 browser smoke 中覆盖 合法 path、非 G-code path fallback 或 reject 行为。仍不解析或改写 G-code 语义。 + +九、2026-06-14 继续执行记录:OPFS G-code program path helper + +本轮继续沿 OPFS/session/UI boundary 做小步实质推进,把 G-code OPFS path 到 filename +的转换下沉到 OPFS G-code store,避免该 path 规则散落在 UI 层。 + +完成内容: + +- 在 `wasm-port/runtime/opfs/machine-file-store.js` 新增 + `gcodeFilenameFromProgramPath(path)`; +- 该 helper 使用既有 `splitOpfsPath(...)` 校验 OPFS path,且只接受 + `linuxcnc/gcode/` 这种单文件 G-code program path; +- 非 G-code program path 或嵌套 G-code path 会 reject,避免 browser/UI 调用方静默接受 + 错误 session 文件清单; +- `wasm-port/runtime/ui/ini-panel/app.js` 的 `Run G-code` 选择逻辑改为复用该 helper; +- UI 仍保持安全 fallback:snapshot 无 G-code path 或 G-code path 非法时回退默认 + `GCODE_FILENAME`,不解释或改写 G-code 内容; +- Node OPFS smoke 覆盖合法 G-code OPFS path、非 G-code path reject、嵌套 G-code path + reject; +- browser INI panel smoke 覆盖合法 G-code OPFS path 转 filename 和非法 OPFS path reject; +- 修正了 snapshot G-code filename 与默认 filename 相同时的来源判定,helper 成功解析即 + `Program source: snapshot`; +- 未改变 G-code 文本内容、OPFS file-service、interpreter run API、canonical-event 解析、 + path model 基础语义或 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 +``` + +下一步建议: + +继续沿 OPFS/session/UI boundary 推进。优先把 UI 的 session snapshot 常量 +`SESSION_SNAPSHOT_ID`、`SESSION_SNAPSHOT_FILENAME` 与 G-code fallback metadata 整理成 +一个小的 UI session descriptor/helper,减少按钮 handler 间重复拼接 snapshot label、 +G-code OPFS path 和默认 program source 的逻辑。仍不解析或改写任何 CNC 语义。 diff --git a/wasm-port/runtime/opfs/machine-file-store.js b/wasm-port/runtime/opfs/machine-file-store.js index 94528d4..8d94836 100644 --- a/wasm-port/runtime/opfs/machine-file-store.js +++ b/wasm-port/runtime/opfs/machine-file-store.js @@ -3,9 +3,12 @@ import { gcodeProgramPath, machineIniPath, parameterFilePath, + splitOpfsPath, toolTablePath, } from "./path-model.js"; +const GCODE_PROGRAM_ROOT = ["linuxcnc", "gcode"]; + const MACHINE_FILE_PATHS = { ini: machineIniPath, toolTable: toolTablePath, @@ -43,6 +46,18 @@ export async function loadMachineTextFiles(machineId, options = {}) { }; } +export function gcodeFilenameFromProgramPath(path) { + const parts = splitOpfsPath(path); + if ( + parts.length !== 3 || + parts[0] !== GCODE_PROGRAM_ROOT[0] || + parts[1] !== GCODE_PROGRAM_ROOT[1] + ) { + throw new Error(`Invalid G-code program OPFS path: ${path}`); + } + return parts[2]; +} + export async function saveGcodeProgram(filename, text, options = {}) { const path = gcodeProgramPath(filename); await saveTextFile(path, text, options.storage); diff --git a/wasm-port/runtime/ui/ini-panel/app.js b/wasm-port/runtime/ui/ini-panel/app.js index 211d021..0be8fd3 100644 --- a/wasm-port/runtime/ui/ini-panel/app.js +++ b/wasm-port/runtime/ui/ini-panel/app.js @@ -8,6 +8,7 @@ import { saveTextFile, } from "../../opfs/file-service.js"; import { + gcodeFilenameFromProgramPath, loadGcodeProgram, loadMachineTextFiles, machineFilePaths, @@ -311,23 +312,24 @@ function syncEditorToWasmFs() { requireIniSdk().writeTextFile(WASM_FILE, editor.value); } -function gcodeFilenameFromSnapshotPath(path) { - const prefix = "linuxcnc/gcode/"; - if (typeof path !== "string" || !path.startsWith(prefix)) { - return GCODE_FILENAME; - } - const filename = path.slice(prefix.length); - return filename || GCODE_FILENAME; -} - async function loadSelectedGcodeProgram() { const opfsPath = restoredSessionSnapshot?.payload?.files?.gcode; - const filename = gcodeFilenameFromSnapshotPath(opfsPath); - const hasSnapshotGcode = typeof opfsPath === "string" && opfsPath.startsWith("linuxcnc/gcode/"); + let filename = GCODE_FILENAME; + let selectedOpfsPath = `linuxcnc/gcode/${GCODE_FILENAME}`; + let source = "default"; + if (typeof opfsPath === "string") { + try { + filename = gcodeFilenameFromProgramPath(opfsPath); + selectedOpfsPath = opfsPath; + source = "snapshot"; + } catch { + filename = GCODE_FILENAME; + } + } return { filename, - opfsPath: opfsPath ?? `linuxcnc/gcode/${filename}`, - source: hasSnapshotGcode ? "snapshot" : "default", + opfsPath: selectedOpfsPath, + source, wasmPath: GCODE_WASM_FILE, text: await loadGcodeProgram(filename), }; diff --git a/wasm-port/tests/browser/ini_panel_smoke.html b/wasm-port/tests/browser/ini_panel_smoke.html index 9ff65b4..a931a00 100644 --- a/wasm-port/tests/browser/ini_panel_smoke.html +++ b/wasm-port/tests/browser/ini_panel_smoke.html @@ -22,6 +22,7 @@ saveSessionSnapshot, } from "../../runtime/opfs/snapshot-store.js"; import { + gcodeFilenameFromProgramPath, loadGcodeProgram, loadMachineTextFiles, saveGcodeProgram, @@ -328,6 +329,16 @@ TOOL_TABLE = browser-tool.tbl "linuxcnc/gcode/browser-custom.ngc", "custom G-code path", ); + assertEqual( + gcodeFilenameFromProgramPath("linuxcnc/gcode/browser-custom.ngc"), + "browser-custom.ngc", + "custom G-code filename from OPFS path", + ); + await assertRejects( + "browser G-code invalid program OPFS path", + () => Promise.resolve(gcodeFilenameFromProgramPath("linuxcnc/machines/browser-custom.ngc")), + /Invalid G-code program OPFS path/, + ); await saveGcodeProgram("browser-smoke.ngc", "G0 X0 Y0\nM2\n"); assertEqual(await loadGcodeProgram("browser-smoke.ngc"), "G0 X0 Y0\nM2\n", "gcode text"); await saveGcodeProgram("browser-custom.ngc", "G1 X1 F10\nM2\n"); diff --git a/wasm-port/tests/opfs/node/verify_file_service.mjs b/wasm-port/tests/opfs/node/verify_file_service.mjs index 0d3fcd1..9dd900c 100644 --- a/wasm-port/tests/opfs/node/verify_file_service.mjs +++ b/wasm-port/tests/opfs/node/verify_file_service.mjs @@ -25,6 +25,7 @@ import { validateSessionSnapshot, } from "../../../runtime/opfs/snapshot-store.js"; import { + gcodeFilenameFromProgramPath, loadGcodeProgram, loadMachineTextFiles, machineFilePaths, @@ -201,6 +202,18 @@ assertGcodeProgramStoragePath( "custom-fixture.ngc", gcodeProgramPath("custom-fixture.ngc"), ); +assert.equal( + gcodeFilenameFromProgramPath("linuxcnc/gcode/fixture.ngc"), + "fixture.ngc", +); +assert.throws( + () => gcodeFilenameFromProgramPath("linuxcnc/machines/xyzab-tdr/fixture.ngc"), + /Invalid G-code program OPFS path/, +); +assert.throws( + () => gcodeFilenameFromProgramPath("linuxcnc/gcode/nested/fixture.ngc"), + /Invalid G-code program OPFS path/, +); assertPreviewCacheStoragePath("fixture", previewCachePath("fixture")); assertPreviewCacheStoragePath( "fixture",