推进 OPFS G-code 程序路径 helper
This commit is contained in:
61
text10.txt
61
text10.txt
@@ -732,3 +732,64 @@ host_wasm_opfs_browser_smokes=ok
|
||||
`linuxcnc/gcode/<filename>` 到 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/<filename>` 这种单文件 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 语义。
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user