UNPKG

opfs-tools

Version:

EN: A simple, high-performance, and comprehensive file system API running in the browser, built on [OPFS](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system).

516 lines (515 loc) 16.3 kB
var U = (r) => { throw TypeError(r); }; var O = (r, e, t) => e.has(r) || U("Cannot " + t); var s = (r, e, t) => (O(r, e, "read from private field"), t ? t.call(r) : e.get(r)), o = (r, e, t) => e.has(r) ? U("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, t), l = (r, e, t, a) => (O(r, e, "write to private field"), a ? a.call(r, t) : e.set(r, t), t); const K = "KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIHUobil7aWYobj09PSIvIilyZXR1cm57cGFyZW50Om51bGwsbmFtZToiIn07Y29uc3QgZT1uLnNwbGl0KCIvIikuZmlsdGVyKGk9PmkubGVuZ3RoPjApO2lmKGUubGVuZ3RoPT09MCl0aHJvdyBFcnJvcigiSW52YWxpZCBwYXRoIik7Y29uc3QgYT1lW2UubGVuZ3RoLTFdLHI9Ii8iK2Uuc2xpY2UoMCwtMSkuam9pbigiLyIpO3JldHVybntuYW1lOmEscGFyZW50OnJ9fWFzeW5jIGZ1bmN0aW9uIHcobixlKXtjb25zdHtwYXJlbnQ6YSxuYW1lOnJ9PXUobik7aWYoYT09bnVsbClyZXR1cm4gYXdhaXQgbmF2aWdhdG9yLnN0b3JhZ2UuZ2V0RGlyZWN0b3J5KCk7Y29uc3QgaT1hLnNwbGl0KCIvIikuZmlsdGVyKHQ9PnQubGVuZ3RoPjApO3RyeXtsZXQgdD1hd2FpdCBuYXZpZ2F0b3Iuc3RvcmFnZS5nZXREaXJlY3RvcnkoKTtmb3IoY29uc3QgcyBvZiBpKXQ9YXdhaXQgdC5nZXREaXJlY3RvcnlIYW5kbGUocyx7Y3JlYXRlOmUuY3JlYXRlfSk7aWYoZS5pc0ZpbGUpcmV0dXJuIGF3YWl0IHQuZ2V0RmlsZUhhbmRsZShyLHtjcmVhdGU6ZS5jcmVhdGV9KX1jYXRjaCh0KXtpZih0Lm5hbWU9PT0iTm90Rm91bmRFcnJvciIpcmV0dXJuIG51bGw7dGhyb3cgdH19Y29uc3QgZj17fTtzZWxmLm9ubWVzc2FnZT1hc3luYyBuPT57dmFyIGk7Y29uc3R7ZXZ0VHlwZTplLGFyZ3M6YX09bi5kYXRhO2xldCByPWZbYS5maWxlSWRdO3RyeXtsZXQgdDtjb25zdCBzPVtdO2lmKGU9PT0icmVnaXN0ZXIiKXtjb25zdCBsPWF3YWl0IHcoYS5maWxlUGF0aCx7Y3JlYXRlOiEwLGlzRmlsZTohMH0pO2lmKGw9PW51bGwpdGhyb3cgRXJyb3IoYG5vdCBmb3VuZCBmaWxlOiAke2EuZmlsZUlkfWApO3I9YXdhaXQgbC5jcmVhdGVTeW5jQWNjZXNzSGFuZGxlKHttb2RlOmEubW9kZX0pLGZbYS5maWxlSWRdPXJ9ZWxzZSBpZihlPT09ImNsb3NlIilhd2FpdCByLmNsb3NlKCksZGVsZXRlIGZbYS5maWxlSWRdO2Vsc2UgaWYoZT09PSJ0cnVuY2F0ZSIpYXdhaXQgci50cnVuY2F0ZShhLm5ld1NpemUpO2Vsc2UgaWYoZT09PSJ3cml0ZSIpe2NvbnN0e2RhdGE6bCxvcHRzOm99PW4uZGF0YS5hcmdzO3Q9YXdhaXQgci53cml0ZShsLG8pfWVsc2UgaWYoZT09PSJyZWFkIil7Y29uc3R7b2Zmc2V0Omwsc2l6ZTpvfT1uLmRhdGEuYXJncyxnPW5ldyBVaW50OEFycmF5KG8pLGQ9YXdhaXQgci5yZWFkKGcse2F0Omx9KSxjPWcuYnVmZmVyO3Q9ZD09PW8/YzooKGk9Yy50cmFuc2Zlcik9PW51bGw/dm9pZCAwOmkuY2FsbChjLGQpKT8/Yy5zbGljZSgwLGQpLHMucHVzaCh0KX1lbHNlIGU9PT0iZ2V0U2l6ZSI/dD1hd2FpdCByLmdldFNpemUoKTplPT09ImZsdXNoIiYmYXdhaXQgci5mbHVzaCgpO3NlbGYucG9zdE1lc3NhZ2Uoe2V2dFR5cGU6ImNhbGxiYWNrIixjYklkOm4uZGF0YS5jYklkLHJldHVyblZhbDp0fSxzKX1jYXRjaCh0KXtjb25zdCBzPXQ7c2VsZi5wb3N0TWVzc2FnZSh7ZXZ0VHlwZToidGhyb3dFcnJvciIsY2JJZDpuLmRhdGEuY2JJZCxlcnJNc2c6cy5uYW1lKyI6ICIrcy5tZXNzYWdlK2AKYCtKU09OLnN0cmluZ2lmeShuLmRhdGEpfSl9fX0pKCk7Ci8vIyBzb3VyY2VNYXBwaW5nVVJMPW9wZnMtd29ya2VyLUY0UldscWNfLmpzLm1hcAo=", Q = (r) => Uint8Array.from(atob(r), (e) => e.charCodeAt(0)), C = typeof self < "u" && self.Blob && new Blob([Q(K)], { type: "text/javascript;charset=utf-8" }); function D(r) { let e; try { if (e = C && (self.URL || self.webkitURL).createObjectURL(C), !e) throw ""; const t = new Worker(e, { name: r == null ? void 0 : r.name }); return t.addEventListener("error", () => { (self.URL || self.webkitURL).revokeObjectURL(e); }), t; } catch { return new Worker( "data:text/javascript;base64," + K, { name: r == null ? void 0 : r.name } ); } finally { e && (self.URL || self.webkitURL).revokeObjectURL(e); } } async function M(r, e, t) { const a = _(); return await a("register", { fileId: r, filePath: e, mode: t }), { read: async (i, n) => await a("read", { fileId: r, offset: i, size: n }), write: async (i, n) => await a( "write", { fileId: r, data: i, opts: n }, [ArrayBuffer.isView(i) ? i.buffer : i] ), close: async () => await a("close", { fileId: r }), truncate: async (i) => await a("truncate", { fileId: r, newSize: i }), getSize: async () => await a("getSize", { fileId: r }), flush: async () => await a("flush", { fileId: r }) }; } const I = []; let k = 0; function _() { if (I.length < 3) { const e = r(); return I.push(e), e; } else { const e = I[k]; return k = (k + 1) % I.length, e; } function r() { const e = new D(); let t = 0, a = {}; return e.onmessage = ({ data: i }) => { var n, c; i.evtType === "callback" ? (n = a[i.cbId]) == null || n.resolve(i.returnVal) : i.evtType === "throwError" && ((c = a[i.cbId]) == null || c.reject(Error(i.errMsg))), delete a[i.cbId]; }, async function(n, c, u = []) { t += 1; const w = new Promise((y, V) => { a[t] = { resolve: y, reject: V }; }); return e.postMessage( { cbId: t, evtType: n, args: c }, u ), w; }; } } function T(r) { if (r === "/") return { parent: null, name: "" }; const e = r.split("/").filter((i) => i.length > 0); if (e.length === 0) throw Error("Invalid path"); const t = e[e.length - 1], a = "/" + e.slice(0, -1).join("/"); return { name: t, parent: a }; } async function d(r, e) { const { parent: t, name: a } = T(r); if (t == null) return await navigator.storage.getDirectory(); const i = t.split("/").filter((n) => n.length > 0); try { let n = await navigator.storage.getDirectory(); for (const c of i) n = await n.getDirectoryHandle(c, { create: e.create }); return e.isFile ? await n.getFileHandle(a, { create: e.create }) : await n.getDirectoryHandle(a, { create: e.create }); } catch (n) { if (n.name === "NotFoundError") return null; throw n; } } async function J(r) { const { parent: e, name: t } = T(r); if (e == null) { const i = await navigator.storage.getDirectory(); for await (const n of i.keys()) await i.removeEntry(n, { recursive: !0 }); return; } const a = await d(e, { create: !1, isFile: !1 }); a != null && await a.removeEntry(t, { recursive: !0 }); } function x(r, e) { return `${r}/${e}`.replace("//", "/"); } function g(r) { return new v(r); } var h, S, b; const H = class H { constructor(e) { o(this, h); o(this, S); o(this, b); l(this, h, e); const { parent: t, name: a } = T(e); l(this, S, a), l(this, b, t); } get kind() { return "dir"; } get name() { return s(this, S); } get path() { return s(this, h); } get parent() { return s(this, b) == null ? null : g(s(this, b)); } /** * Creates the directory. * return A promise that resolves when the directory is created. */ async create() { return await d(s(this, h), { create: !0, isFile: !1 }), g(s(this, h)); } /** * Checks if the directory exists. * return A promise that resolves to true if the directory exists, otherwise false. */ async exists() { return await d(s(this, h), { create: !1, isFile: !1 }) instanceof FileSystemDirectoryHandle; } /** * Removes the directory. * return A promise that resolves when the directory is removed. */ async remove() { for (const e of await this.children()) try { await e.remove(); } catch (t) { console.warn(t); } try { await J(s(this, h)); } catch (e) { console.warn(e); } } /** * Retrieves the children of the directory. * return A promise that resolves to an array of objects representing the children. */ async children() { const e = await d(s(this, h), { create: !1, isFile: !1 }); if (e == null) return []; const t = []; for await (const a of e.values()) t.push((a.kind === "file" ? F : g)(x(s(this, h), a.name))); return t; } /** * If the dest folder exists, copy the current directory into the dest folder; * if the dest folder does not exist, rename the current directory to dest name. */ async copyTo(e) { if (!await this.exists()) throw Error(`dir ${this.path} not exists`); if (e instanceof H) { const t = await e.exists() ? g(x(e.path, this.name)) : e; await t.create(), await Promise.all((await this.children()).map((a) => a.copyTo(t))); return; } else if (e instanceof FileSystemDirectoryHandle) { await Promise.all( (await this.children()).map(async (t) => { t.kind === "file" ? await t.copyTo( await e.getFileHandle(t.name, { create: !0 }) ) : await t.copyTo( await e.getDirectoryHandle(t.name, { create: !0 }) ); }) ); return; } throw Error("Illegal target type"); } /** * move directory, copy then remove current */ async moveTo(e) { await this.copyTo(e), await this.remove(); } }; h = new WeakMap(), S = new WeakMap(), b = new WeakMap(); let v = H; const z = /* @__PURE__ */ new Map(); function F(r, e = "rw") { if (e === "rw") { const t = z.get(r) ?? new W(r, e); return z.set(r, t), t; } return new W(r, e); } async function N(r, e, t = { overwrite: !0 }) { if (e instanceof W) { await N(r, await e.stream(), t); return; } const a = await (r instanceof W ? r : F(r, "rw")).createWriter(); try { if (t.overwrite && await a.truncate(0), e instanceof ReadableStream) { const i = e.getReader(); for (; ; ) { const { done: n, value: c } = await i.read(); if (n) break; await a.write(c); } } else await a.write(e); } catch (i) { throw i; } finally { await a.close(); } } let A = 0; const $ = () => ++A; var f, p, G, Z, X, m, R, Y; const P = class P { constructor(e, t) { o(this, f); o(this, p); o(this, G); o(this, Z); o(this, X); o(this, m, 0); o(this, R, /* @__PURE__ */ (() => { let e = null; return () => (l(this, m, s(this, m) + 1), e ?? (e = new Promise(async (t, a) => { try { const i = await M( s(this, X), s(this, f), s(this, Z) ); t([ i, async () => { l(this, m, s(this, m) - 1), !(s(this, m) > 0) && (e = null, await i.close()); } ]); } catch (i) { a(i); } }))); })()); o(this, Y, !1); l(this, X, $()), l(this, f, e), l(this, Z, { r: "read-only", rw: "readwrite", "rw-unsafe": "readwrite-unsafe" }[t]); const { parent: a, name: i } = T(e); if (a == null) throw Error("Invalid path"); l(this, G, i), l(this, p, a); } get kind() { return "file"; } get path() { return s(this, f); } get name() { return s(this, G); } get parent() { return s(this, p) == null ? null : g(s(this, p)); } /** * Random write to file */ async createWriter() { if (s(this, Z) === "read-only") throw Error("file is read-only"); if (s(this, Y)) throw Error("Other writer have not been closed"); l(this, Y, !0); const e = new TextEncoder(), [t, a] = await s(this, R).call(this); let i = await t.getSize(), n = !1; return { write: async (c, u = {}) => { if (n) throw Error("Writer is closed"); const w = typeof c == "string" ? e.encode(c) : c, y = u.at ?? i, V = w.byteLength; return i = y + V, await t.write(w, { at: y }); }, truncate: async (c) => { if (n) throw Error("Writer is closed"); await t.truncate(c), i > c && (i = c); }, flush: async () => { if (n) throw Error("Writer is closed"); await t.flush(); }, close: async () => { if (n) throw Error("Writer is closed"); n = !0, l(this, Y, !1), await a(); } }; } /** * Random access to file */ async createReader() { const [e, t] = await s(this, R).call(this); let a = !1, i = 0; return { read: async (n, c = {}) => { if (a) throw Error("Reader is closed"); const u = c.at ?? i, w = await e.read(u, n); return i = u + w.byteLength, w; }, getSize: async () => { if (a) throw Error("Reader is closed"); return await e.getSize(); }, close: async () => { a || (a = !0, await t()); } }; } async text() { return new TextDecoder().decode(await this.arrayBuffer()); } async arrayBuffer() { const e = await d(s(this, f), { create: !1, isFile: !0 }); return e == null ? new ArrayBuffer(0) : (await e.getFile()).arrayBuffer(); } async stream() { const e = await this.getOriginFile(); return e == null ? new ReadableStream({ pull: (t) => { t.close(); } }) : e.stream(); } async getOriginFile() { var e; return (e = await d(s(this, f), { create: !1, isFile: !0 })) == null ? void 0 : e.getFile(); } async getSize() { const e = await d(s(this, f), { create: !1, isFile: !0 }); return e == null ? 0 : (await e.getFile()).size; } async exists() { return await d(s(this, f), { create: !1, isFile: !0 }) instanceof FileSystemFileHandle; } async remove() { if (s(this, m)) throw Error("exists unclosed reader/writer"); await J(s(this, f)); } /** * If the target is a file, use current overwrite the target; * if the target is a folder, copy the current file into that folder. */ async copyTo(e) { if (e instanceof P) { if (e.path === this.path) return; await N(e.path, this); return; } else if (e instanceof v) { if (!await this.exists()) throw Error(`file ${this.path} not exists`); return await this.copyTo(F(x(e.path, this.name))); } else if (e instanceof FileSystemFileHandle) { await (await this.stream()).pipeTo(await e.createWritable()); return; } throw Error("Illegal target type"); } /** * move file, copy then remove current */ async moveTo(e) { await this.copyTo(e), await this.remove(); } }; f = new WeakMap(), p = new WeakMap(), G = new WeakMap(), Z = new WeakMap(), X = new WeakMap(), m = new WeakMap(), R = new WeakMap(), Y = new WeakMap(); let W = P; const E = "/.opfs-tools-temp-dir"; async function B(r) { try { if (r.kind === "file") { if (!await r.exists()) return !0; const e = await r.createWriter(); await e.truncate(0), await e.close(), await r.remove(); } else await r.remove(); return !0; } catch (e) { return console.warn(e), !1; } } function q() { setInterval(async () => { for (const e of await g(E).children()) { const t = /^\d+-(\d+)$/.exec(e.name); (t == null || Date.now() - Number(t[1]) > 2592e5) && await B(e); } }, 60 * 1e3); } const L = []; let j = !1; async function ee() { if (globalThis.localStorage == null) return; const r = "OPFS_TOOLS_EXPIRES_TMP_FILES"; j || (j = !0, globalThis.addEventListener("unload", () => { L.length !== 0 && localStorage.setItem( r, `${localStorage.getItem(r) ?? ""},${L.join(",")}` ); })); let e = localStorage.getItem(r) ?? ""; for (const t of e.split(",")) t.length !== 0 && await B(F(`${E}/${t}`)) && (e = e.replace(t, "")); localStorage.setItem(r, e.replace(/,{2,}/g, ",")); } (async function() { var e; globalThis.__opfs_tools_tmpfile_init__ !== !0 && (globalThis.__opfs_tools_tmpfile_init__ = !0, !(globalThis.FileSystemDirectoryHandle == null || globalThis.FileSystemFileHandle == null || ((e = globalThis.navigator) == null ? void 0 : e.storage.getDirectory) == null) && (q(), await ee())); })(); function re() { const r = `${Math.random().toString().slice(2)}-${Date.now()}`; return L.push(r), F(`${E}/${r}`); } function ae(r, e) { let t = F(r), a = 0, i = t.createWriter(), n = t.createReader(); const c = async (u) => { const y = await (await n).read(a, { at: Math.round(a * 0.3) }); a = await u.write(y, { at: 0 }), await u.truncate(a); }; return { append: async (u) => { const w = await i; a += await w.write(u), a >= e && await c(w); }, text: t.text.bind(t), remove: async () => { await (await n).close(), await (await i).close(), await t.remove(); }, getSize: async () => a }; } export { g as dir, F as file, ae as rollfile, re as tmpfile, N as write }; //# sourceMappingURL=opfs-tools.js.map