diff --git a/src/kiri/app/api.js b/src/kiri/app/api.js index 275a123b..aa5ea324 100644 --- a/src/kiri/app/api.js +++ b/src/kiri/app/api.js @@ -40,6 +40,8 @@ import { visuals } from './visuals.js'; import { widgets } from './widgets.js'; import { workspace } from './workspace.js'; +import { OPFS } from '../../moto/opfs.js'; + // environment setup let LOC = self.location, EVENT = broker, @@ -228,6 +230,7 @@ export const api = { onkey(fn) { api.feature.on_key2.push(fn); }, + opfs: OPFS, platform, process: processModule, sdb: dataLocal, diff --git a/src/moto/opfs.js b/src/moto/opfs.js new file mode 100644 index 00000000..5b23196b --- /dev/null +++ b/src/moto/opfs.js @@ -0,0 +1,121 @@ +const root = await navigator.storage.getDirectory(); + +function resolvePath(path) { + if (!path) { + return []; + } else if (typeof path === 'string') { + path = path.trim(); + while (path.charAt(0) === '/') { + path = path.substring(1); + } + return path.length ? path.split('/') : []; + } else if (Array.isArray(path)) { + return path; + } else { + throw "invalid path value"; + } +} + +export async function dirHandle(path, options = { create: true }) { + path = resolvePath(path); + if (path.length === 0) { + return root; + } + let dir = root; + for (let tok of path) { + try { + dir = await dir.getDirectoryHandle(tok, options); + } catch (error) { + if (options.report) { + console.log({ path, error }); + } + return undefined; + } + } + return dir; +} + +export async function fileHandle(path, options = { create: false }) { + path = resolvePath(path); + if (path.length === 0) { + return undefined; + } + let target = path.pop(); + let dir = await dirHandle(path); + return dir.getFileHandle(target, options); +} + +export async function clear() { + for await (let name of root.keys()) { + await root.removeEntry(name, { recursive: true }); + } +} + +export async function remove(path) { + path = resolvePath(path); + if (path.length === 0) { + return clear(); + } + let target = path.pop(); + let dir = await dirHandle(path); + console.log({ dir, target }); + await dir.removeEntry(target, { recursive: true }); +} + +export async function entries(path) { + return (await dirHandle(path)).entries(); +} + +export async function values(path) { + return (await dirHandle(path)).values(); +} + +export async function keys(path) { + return (await dirHandle(path)).keys(); +} + +export async function tree(path) { + path = resolvePath(path); + let dir = await dirHandle(path); + return treeFrom(dir, path, []); +} + +async function treeFrom(dir, path, list) { + let entries = await dir.entries() + for await (let [ name, handle ] of entries) { + path.push(name); + if (handle.kind === 'directory') { + await treeFrom(handle, path, list); + } else { + list.push(path.join('/')); + } + path.pop(); + } + return list; +} + +export async function getText(path) { + let handle = await fileHandle(path); + let file = await handle.getFile(); + return file.text(); +} + +export async function putText(path, text) { + let handle = await fileHandle(path, { create: true }); + let stream = await handle.createWritable(); + await stream.write(text); + return stream.close(); +} + +export const OPFS = { + dirHandle, + fileHandle, + entries, + keys, + values, + tree, + clear, + remove, + getText, + putText +};