按建议,继续完成后续工作
结论:已将 INI 面板的 OPFS 文本文件读写抽为 host-side file-service,并新增 Node mock 验证,确认 OPFS 边界不进入 WASM core 且可覆盖保存、读取、缺失路径和非法路径行为。
This commit is contained in:
52
wasm-port/runtime/opfs/file-service.js
Normal file
52
wasm-port/runtime/opfs/file-service.js
Normal file
@@ -0,0 +1,52 @@
|
||||
function splitPath(path) {
|
||||
if (typeof path !== "string" || path.length === 0) {
|
||||
throw new Error("OPFS path must be a non-empty relative path.");
|
||||
}
|
||||
if (path.startsWith("/") || path.includes("//")) {
|
||||
throw new Error(`Invalid OPFS path: ${path}`);
|
||||
}
|
||||
|
||||
const parts = path.split("/");
|
||||
if (parts.some((part) => part === "." || part === ".." || part === "")) {
|
||||
throw new Error(`Invalid OPFS path: ${path}`);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
export async function getOpfsRoot(storage = globalThis.navigator?.storage) {
|
||||
if (!storage?.getDirectory) {
|
||||
throw new Error("OPFS is not available in this browser.");
|
||||
}
|
||||
return storage.getDirectory();
|
||||
}
|
||||
|
||||
export async function ensureParentDir(root, path) {
|
||||
const parts = splitPath(path);
|
||||
let current = root;
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
current = await current.getDirectoryHandle(part, { create: true });
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
export async function saveTextFile(path, text, storage) {
|
||||
const root = await getOpfsRoot(storage);
|
||||
const dir = await ensureParentDir(root, path);
|
||||
const filename = splitPath(path).at(-1);
|
||||
const fileHandle = await dir.getFileHandle(filename, { create: true });
|
||||
const writable = await fileHandle.createWritable();
|
||||
await writable.write(text);
|
||||
await writable.close();
|
||||
}
|
||||
|
||||
export async function loadTextFile(path, storage) {
|
||||
const root = await getOpfsRoot(storage);
|
||||
const parts = splitPath(path);
|
||||
let current = root;
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
current = await current.getDirectoryHandle(part);
|
||||
}
|
||||
const fileHandle = await current.getFileHandle(parts.at(-1));
|
||||
const file = await fileHandle.getFile();
|
||||
return file.text();
|
||||
}
|
||||
4
wasm-port/runtime/opfs/package.json
Normal file
4
wasm-port/runtime/opfs/package.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module"
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
import createLinuxCncIniModule from "./linuxcnc_ini.js";
|
||||
import {
|
||||
getOpfsRoot,
|
||||
loadTextFile,
|
||||
saveTextFile,
|
||||
} from "../../opfs/file-service.js";
|
||||
|
||||
const SAMPLE_PATH =
|
||||
"../../../linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini";
|
||||
@@ -70,44 +75,6 @@ function iniQuery(mod, wasmPath, section, tag) {
|
||||
}
|
||||
}
|
||||
|
||||
async function getOpfsRoot() {
|
||||
if (!navigator.storage?.getDirectory) {
|
||||
throw new Error("OPFS is not available in this browser.");
|
||||
}
|
||||
return navigator.storage.getDirectory();
|
||||
}
|
||||
|
||||
async function ensureParentDir(root, path) {
|
||||
const parts = path.split("/");
|
||||
let current = root;
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
current = await current.getDirectoryHandle(part, { create: true });
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
async function saveToOpfs(path, text) {
|
||||
const root = await getOpfsRoot();
|
||||
const dir = await ensureParentDir(root, path);
|
||||
const filename = path.split("/").at(-1);
|
||||
const fileHandle = await dir.getFileHandle(filename, { create: true });
|
||||
const writable = await fileHandle.createWritable();
|
||||
await writable.write(text);
|
||||
await writable.close();
|
||||
}
|
||||
|
||||
async function loadFromOpfs(path) {
|
||||
const root = await getOpfsRoot();
|
||||
const parts = path.split("/");
|
||||
let current = root;
|
||||
for (const part of parts.slice(0, -1)) {
|
||||
current = await current.getDirectoryHandle(part);
|
||||
}
|
||||
const fileHandle = await current.getFileHandle(parts.at(-1));
|
||||
const file = await fileHandle.getFile();
|
||||
return file.text();
|
||||
}
|
||||
|
||||
function syncEditorToWasmFs() {
|
||||
const mod = requireModule();
|
||||
try {
|
||||
@@ -157,7 +124,7 @@ document.getElementById("load-sample").addEventListener("click", async () => {
|
||||
|
||||
document.getElementById("save-opfs").addEventListener("click", async () => {
|
||||
try {
|
||||
await saveToOpfs(OPFS_FILE, editor.value);
|
||||
await saveTextFile(OPFS_FILE, editor.value);
|
||||
setLog(`Saved current INI text to OPFS at ${OPFS_FILE}`);
|
||||
} catch (error) {
|
||||
setLog(`OPFS save failed: ${error.message}`, true);
|
||||
@@ -166,7 +133,7 @@ document.getElementById("save-opfs").addEventListener("click", async () => {
|
||||
|
||||
document.getElementById("load-opfs").addEventListener("click", async () => {
|
||||
try {
|
||||
editor.value = await loadFromOpfs(OPFS_FILE);
|
||||
editor.value = await loadTextFile(OPFS_FILE);
|
||||
setLog(`Loaded INI text from OPFS path ${OPFS_FILE}`);
|
||||
} catch (error) {
|
||||
setLog(`OPFS load failed: ${error.message}`, true);
|
||||
|
||||
Reference in New Issue
Block a user