// OPFS-backed SD card store — the source of truth for /sdcard contents. // It persists across page reloads, so "edit files, then reboot to apply" works // by reloading the page: edits survive in OPFS and are seeded into the guest's // MEMFS /sdcard on the next boot. Guest-side writes remain ephemeral. const SD_DIR = 'sdcard'; async function sdRoot() { const root = await navigator.storage.getDirectory(); return root.getDirectoryHandle(SD_DIR, { create: true }); } async function resolveParent(path, create) { const parts = path.split('/').filter(Boolean); const filename = parts.pop(); let dir = await sdRoot(); for (const part of parts) { dir = await dir.getDirectoryHandle(part, { create }); } return [dir, filename]; } export async function listSdFiles() { const entries = []; const walk = async (dir, prefix) => { for await (const [name, handle] of dir.entries()) { const path = prefix ? `${prefix}/${name}` : name; if (handle.kind === 'directory') { entries.push({ path: `${path}/`, directory: true }); await walk(handle, path); } else { entries.push({ path, size: (await handle.getFile()).size }); } } }; await walk(await sdRoot(), ''); entries.sort((a, b) => a.path.localeCompare(b.path)); return entries; } export async function readSdFile(path) { const [dir, name] = await resolveParent(path, false); const handle = await dir.getFileHandle(name); return new Uint8Array(await (await handle.getFile()).arrayBuffer()); } export async function writeSdFile(path, data) { const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; const [dir, name] = await resolveParent(path, true); const handle = await dir.getFileHandle(name, { create: true }); const writable = await handle.createWritable(); await writable.write(bytes); await writable.close(); } // ponytail: empty dirs live only in OPFS; readAllSdFiles skips them, so they vanish on reboot. Add a placeholder file if guest-side empty dirs ever matter. export async function mkdirSd(path) { let dir = await sdRoot(); for (const part of path.split('/').filter(Boolean)) { dir = await dir.getDirectoryHandle(part, { create: true }); } } export async function deleteSdFile(path) { const [dir, name] = await resolveParent(path, false); await dir.removeEntry(name, { recursive: true }); } export async function clearSdFiles() { const root = await navigator.storage.getDirectory(); await root.removeEntry(SD_DIR, { recursive: true }); } // Flatten to [{ path, bytes }] for seeding the guest's MEMFS /sdcard at boot. export async function readAllSdFiles() { const out = []; for (const entry of await listSdFiles()) { if (!entry.directory) { out.push({ path: entry.path, bytes: await readSdFile(entry.path) }); } } return out; }