推进 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,
|
`linuxcnc/gcode/<filename>` 到 filename 的转换下沉为 OPFS G-code store 的可复用 helper,
|
||||||
例如 `gcodeFilenameFromProgramPath(...)`,并在 Node OPFS smoke 与 browser smoke 中覆盖
|
例如 `gcodeFilenameFromProgramPath(...)`,并在 Node OPFS smoke 与 browser smoke 中覆盖
|
||||||
合法 path、非 G-code path fallback 或 reject 行为。仍不解析或改写 G-code 语义。
|
合法 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,
|
gcodeProgramPath,
|
||||||
machineIniPath,
|
machineIniPath,
|
||||||
parameterFilePath,
|
parameterFilePath,
|
||||||
|
splitOpfsPath,
|
||||||
toolTablePath,
|
toolTablePath,
|
||||||
} from "./path-model.js";
|
} from "./path-model.js";
|
||||||
|
|
||||||
|
const GCODE_PROGRAM_ROOT = ["linuxcnc", "gcode"];
|
||||||
|
|
||||||
const MACHINE_FILE_PATHS = {
|
const MACHINE_FILE_PATHS = {
|
||||||
ini: machineIniPath,
|
ini: machineIniPath,
|
||||||
toolTable: toolTablePath,
|
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 = {}) {
|
export async function saveGcodeProgram(filename, text, options = {}) {
|
||||||
const path = gcodeProgramPath(filename);
|
const path = gcodeProgramPath(filename);
|
||||||
await saveTextFile(path, text, options.storage);
|
await saveTextFile(path, text, options.storage);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
saveTextFile,
|
saveTextFile,
|
||||||
} from "../../opfs/file-service.js";
|
} from "../../opfs/file-service.js";
|
||||||
import {
|
import {
|
||||||
|
gcodeFilenameFromProgramPath,
|
||||||
loadGcodeProgram,
|
loadGcodeProgram,
|
||||||
loadMachineTextFiles,
|
loadMachineTextFiles,
|
||||||
machineFilePaths,
|
machineFilePaths,
|
||||||
@@ -311,23 +312,24 @@ function syncEditorToWasmFs() {
|
|||||||
requireIniSdk().writeTextFile(WASM_FILE, editor.value);
|
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() {
|
async function loadSelectedGcodeProgram() {
|
||||||
const opfsPath = restoredSessionSnapshot?.payload?.files?.gcode;
|
const opfsPath = restoredSessionSnapshot?.payload?.files?.gcode;
|
||||||
const filename = gcodeFilenameFromSnapshotPath(opfsPath);
|
let filename = GCODE_FILENAME;
|
||||||
const hasSnapshotGcode = typeof opfsPath === "string" && opfsPath.startsWith("linuxcnc/gcode/");
|
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 {
|
return {
|
||||||
filename,
|
filename,
|
||||||
opfsPath: opfsPath ?? `linuxcnc/gcode/${filename}`,
|
opfsPath: selectedOpfsPath,
|
||||||
source: hasSnapshotGcode ? "snapshot" : "default",
|
source,
|
||||||
wasmPath: GCODE_WASM_FILE,
|
wasmPath: GCODE_WASM_FILE,
|
||||||
text: await loadGcodeProgram(filename),
|
text: await loadGcodeProgram(filename),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
saveSessionSnapshot,
|
saveSessionSnapshot,
|
||||||
} from "../../runtime/opfs/snapshot-store.js";
|
} from "../../runtime/opfs/snapshot-store.js";
|
||||||
import {
|
import {
|
||||||
|
gcodeFilenameFromProgramPath,
|
||||||
loadGcodeProgram,
|
loadGcodeProgram,
|
||||||
loadMachineTextFiles,
|
loadMachineTextFiles,
|
||||||
saveGcodeProgram,
|
saveGcodeProgram,
|
||||||
@@ -328,6 +329,16 @@ TOOL_TABLE = browser-tool.tbl
|
|||||||
"linuxcnc/gcode/browser-custom.ngc",
|
"linuxcnc/gcode/browser-custom.ngc",
|
||||||
"custom G-code path",
|
"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");
|
await saveGcodeProgram("browser-smoke.ngc", "G0 X0 Y0\nM2\n");
|
||||||
assertEqual(await loadGcodeProgram("browser-smoke.ngc"), "G0 X0 Y0\nM2\n", "gcode text");
|
assertEqual(await loadGcodeProgram("browser-smoke.ngc"), "G0 X0 Y0\nM2\n", "gcode text");
|
||||||
await saveGcodeProgram("browser-custom.ngc", "G1 X1 F10\nM2\n");
|
await saveGcodeProgram("browser-custom.ngc", "G1 X1 F10\nM2\n");
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
validateSessionSnapshot,
|
validateSessionSnapshot,
|
||||||
} from "../../../runtime/opfs/snapshot-store.js";
|
} from "../../../runtime/opfs/snapshot-store.js";
|
||||||
import {
|
import {
|
||||||
|
gcodeFilenameFromProgramPath,
|
||||||
loadGcodeProgram,
|
loadGcodeProgram,
|
||||||
loadMachineTextFiles,
|
loadMachineTextFiles,
|
||||||
machineFilePaths,
|
machineFilePaths,
|
||||||
@@ -201,6 +202,18 @@ assertGcodeProgramStoragePath(
|
|||||||
"custom-fixture.ngc",
|
"custom-fixture.ngc",
|
||||||
gcodeProgramPath("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", previewCachePath("fixture"));
|
||||||
assertPreviewCacheStoragePath(
|
assertPreviewCacheStoragePath(
|
||||||
"fixture",
|
"fixture",
|
||||||
|
|||||||
Reference in New Issue
Block a user