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).
479 lines (478 loc) • 15.2 kB
JavaScript
var H = (r) => {
throw TypeError(r);
};
var L = (r, e, t) => e.has(r) || H("Cannot " + t);
var s = (r, e, t) => (L(r, e, "read from private field"), t ? t.call(r) : e.get(r)), f = (r, e, t) => e.has(r) ? H("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(r) : e.set(r, t), u = (r, e, t, a) => (L(r, e, "write to private field"), a ? a.call(r, t) : e.set(r, t), t);
const K = "KGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO2Z1bmN0aW9uIHUocil7aWYocj09PSIvIilyZXR1cm57cGFyZW50Om51bGwsbmFtZToiIn07Y29uc3QgZT1yLnNwbGl0KCIvIikuZmlsdGVyKGk9PmkubGVuZ3RoPjApO2lmKGUubGVuZ3RoPT09MCl0aHJvdyBFcnJvcigiSW52YWxpZCBwYXRoIik7Y29uc3Qgbj1lW2UubGVuZ3RoLTFdLGE9Ii8iK2Uuc2xpY2UoMCwtMSkuam9pbigiLyIpO3JldHVybntuYW1lOm4scGFyZW50OmF9fWFzeW5jIGZ1bmN0aW9uIGgocixlKXtjb25zdHtwYXJlbnQ6bixuYW1lOmF9PXUocik7aWYobj09bnVsbClyZXR1cm4gYXdhaXQgbmF2aWdhdG9yLnN0b3JhZ2UuZ2V0RGlyZWN0b3J5KCk7Y29uc3QgaT1uLnNwbGl0KCIvIikuZmlsdGVyKHQ9PnQubGVuZ3RoPjApO3RyeXtsZXQgdD1hd2FpdCBuYXZpZ2F0b3Iuc3RvcmFnZS5nZXREaXJlY3RvcnkoKTtmb3IoY29uc3QgcyBvZiBpKXQ9YXdhaXQgdC5nZXREaXJlY3RvcnlIYW5kbGUocyx7Y3JlYXRlOmUuY3JlYXRlfSk7aWYoZS5pc0ZpbGUpcmV0dXJuIGF3YWl0IHQuZ2V0RmlsZUhhbmRsZShhLHtjcmVhdGU6ZS5jcmVhdGV9KX1jYXRjaCh0KXtpZih0Lm5hbWU9PT0iTm90Rm91bmRFcnJvciIpcmV0dXJuIG51bGw7dGhyb3cgdH19Y29uc3QgZj17fTtzZWxmLm9ubWVzc2FnZT1hc3luYyByPT57dmFyIGk7Y29uc3R7ZXZ0VHlwZTplLGFyZ3M6bn09ci5kYXRhO2xldCBhPWZbbi5maWxlUGF0aF07dHJ5e2xldCB0O2NvbnN0IHM9W107aWYoZT09PSJyZWdpc3RlciIpe2NvbnN0IGw9YXdhaXQgaChuLmZpbGVQYXRoLHtjcmVhdGU6ITAsaXNGaWxlOiEwfSk7aWYobD09bnVsbCl0aHJvdyBFcnJvcihgbm90IGZvdW5kIGZpbGU6ICR7bi5maWxlUGF0aH1gKTthPWF3YWl0IGwuY3JlYXRlU3luY0FjY2Vzc0hhbmRsZSgpLGZbbi5maWxlUGF0aF09YX1lbHNlIGlmKGU9PT0iY2xvc2UiKWF3YWl0IGEuY2xvc2UoKSxkZWxldGUgZltuLmZpbGVQYXRoXTtlbHNlIGlmKGU9PT0idHJ1bmNhdGUiKWF3YWl0IGEudHJ1bmNhdGUobi5uZXdTaXplKTtlbHNlIGlmKGU9PT0id3JpdGUiKXtjb25zdHtkYXRhOmwsb3B0czpvfT1yLmRhdGEuYXJnczt0PWF3YWl0IGEud3JpdGUobCxvKX1lbHNlIGlmKGU9PT0icmVhZCIpe2NvbnN0e29mZnNldDpsLHNpemU6b309ci5kYXRhLmFyZ3MsZz1uZXcgVWludDhBcnJheShvKSxkPWF3YWl0IGEucmVhZChnLHthdDpsfSksYz1nLmJ1ZmZlcjt0PWQ9PT1vP2M6KChpPWMudHJhbnNmZXIpPT1udWxsP3ZvaWQgMDppLmNhbGwoYyxkKSk/P2Muc2xpY2UoMCxkKSxzLnB1c2godCl9ZWxzZSBlPT09ImdldFNpemUiP3Q9YXdhaXQgYS5nZXRTaXplKCk6ZT09PSJmbHVzaCImJmF3YWl0IGEuZmx1c2goKTtzZWxmLnBvc3RNZXNzYWdlKHtldnRUeXBlOiJjYWxsYmFjayIsY2JJZDpyLmRhdGEuY2JJZCxyZXR1cm5WYWw6dH0scyl9Y2F0Y2godCl7Y29uc3Qgcz10O3NlbGYucG9zdE1lc3NhZ2Uoe2V2dFR5cGU6InRocm93RXJyb3IiLGNiSWQ6ci5kYXRhLmNiSWQsZXJyTXNnOnMubmFtZSsiOiAiK3MubWVzc2FnZStgCmArSlNPTi5zdHJpbmdpZnkoci5kYXRhKX0pfX19KSgpOwovLyMgc291cmNlTWFwcGluZ1VSTD1vcGZzLXdvcmtlci1CUWh5WXcwVy5qcy5tYXAK", O = (r) => Uint8Array.from(atob(r), (e) => e.charCodeAt(0)), E = typeof self < "u" && self.Blob && new Blob([O(K)], { type: "text/javascript;charset=utf-8" });
function j(r) {
let e;
try {
if (e = E && (self.URL || self.webkitURL).createObjectURL(E), !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) {
const e = B();
return await e("register", { filePath: r }), {
read: async (t, a) => await e("read", {
filePath: r,
offset: t,
size: a
}),
write: async (t, a) => await e(
"write",
{
filePath: r,
data: t,
opts: a
},
[ArrayBuffer.isView(t) ? t.buffer : t]
),
close: async () => await e("close", {
filePath: r
}),
truncate: async (t) => await e("truncate", {
filePath: r,
newSize: t
}),
getSize: async () => await e("getSize", {
filePath: r
}),
flush: async () => await e("flush", {
filePath: r
})
};
}
const I = [];
let T = 0;
function B() {
if (I.length < 3) {
const e = r();
return I.push(e), e;
} else {
const e = I[T];
return T = (T + 1) % I.length, e;
}
function r() {
const e = new j();
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, l = []) {
t += 1;
const o = new Promise((b, X) => {
a[t] = { resolve: b, reject: X };
});
return e.postMessage(
{
cbId: t,
evtType: n,
args: c
},
l
), o;
};
}
}
function R(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 } = R(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 N(r) {
const { parent: e, name: t } = R(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 S(r, e) {
return `${r}/${e}`.replace("//", "/");
}
function g(r) {
return new C(r);
}
var h, Y, p;
class C {
constructor(e) {
f(this, h);
f(this, Y);
f(this, p);
u(this, h, e);
const { parent: t, name: a } = R(e);
u(this, Y, a), u(this, p, t);
}
get kind() {
return "dir";
}
get name() {
return s(this, Y);
}
get path() {
return s(this, h);
}
get parent() {
return s(this, p) == null ? null : g(s(this, p));
}
/**
* 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 N(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" ? m : g)(S(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`);
const t = await e.exists() ? g(S(e.path, this.name)) : e;
return await t.create(), await Promise.all((await this.children()).map((a) => a.copyTo(t))), t;
}
/**
* move directory, copy then remove current
*/
async moveTo(e) {
const t = await this.copyTo(e);
return await this.remove(), t;
}
}
h = new WeakMap(), Y = new WeakMap(), p = new WeakMap();
const J = /* @__PURE__ */ new Map();
function m(r) {
const e = J.get(r) ?? new G(r);
return J.set(r, e), e;
}
async function V(r, e, t = { overwrite: !0 }) {
if (e instanceof G) {
await V(r, await e.stream(), t);
return;
}
const a = await (r instanceof G ? r : m(r)).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();
}
}
var w, Z, F, y, v, W;
const k = class k {
constructor(e) {
f(this, w);
f(this, Z);
f(this, F);
f(this, y, 0);
f(this, v, /* @__PURE__ */ (() => {
let e = null;
return () => (u(this, y, s(this, y) + 1), e ?? (e = new Promise(async (t, a) => {
try {
const i = await M(s(this, w));
t([
i,
async () => {
u(this, y, s(this, y) - 1), !(s(this, y) > 0) && (e = null, await i.close());
}
]);
} catch (i) {
a(i);
}
})));
})());
f(this, W, !1);
u(this, w, e);
const { parent: t, name: a } = R(e);
u(this, F, a), u(this, Z, t);
}
get kind() {
return "file";
}
get path() {
return s(this, w);
}
get name() {
return s(this, F);
}
get parent() {
return s(this, Z) == null ? null : g(s(this, Z));
}
/**
* Random write to file
*/
async createWriter() {
if (s(this, W)) throw Error("Other writer have not been closed");
u(this, W, !0);
const e = new TextEncoder(), [t, a] = await s(this, v).call(this);
let i = await t.getSize(), n = !1;
return {
write: async (c, l = {}) => {
if (n) throw Error("Writer is closed");
const o = typeof c == "string" ? e.encode(c) : c, b = l.at ?? i, X = o.byteLength;
return i = b + X, await t.write(o, { at: b });
},
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, u(this, W, !1), await a();
}
};
}
/**
* Random access to file
*/
async createReader() {
const [e, t] = await s(this, v).call(this);
let a = !1, i = 0;
return {
read: async (n, c = {}) => {
if (a) throw Error("Reader is closed");
const l = c.at ?? i, o = await e.read(l, n);
return i = l + o.byteLength, o;
},
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, w), { 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, w), { create: !1, isFile: !0 })) == null ? void 0 : e.getFile();
}
async getSize() {
const e = await d(s(this, w), { create: !1, isFile: !0 });
return e == null ? 0 : (await e.getFile()).size;
}
async exists() {
return await d(s(this, w), {
create: !1,
isFile: !0
}) instanceof FileSystemFileHandle;
}
async remove() {
if (s(this, y)) throw Error("exists unclosed reader/writer");
await N(s(this, w));
}
/**
* 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 (!await this.exists())
throw Error(`file ${this.path} not exists`);
if (e instanceof k)
return m(e.path) === this ? this : (await V(e.path, this), m(e.path));
if (e instanceof C)
return await this.copyTo(m(S(e.path, this.name)));
throw Error("Illegal target type");
}
/**
* move file, copy then remove current
*/
async moveTo(e) {
const t = await this.copyTo(e);
return await this.remove(), t;
}
};
w = new WeakMap(), Z = new WeakMap(), F = new WeakMap(), y = new WeakMap(), v = new WeakMap(), W = new WeakMap();
let G = k;
const U = "/.opfs-tools-temp-dir";
async function z(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 D() {
setInterval(async () => {
for (const e of await g(U).children()) {
const t = /^\d+-(\d+)$/.exec(e.name);
(t == null || Date.now() - Number(t[1]) > 2592e5) && await z(e);
}
}, 60 * 1e3);
}
const x = [];
let P = !1;
async function Q() {
if (globalThis.localStorage == null) return;
const r = "OPFS_TOOLS_EXPIRES_TMP_FILES";
P || (P = !0, globalThis.addEventListener("unload", () => {
x.length !== 0 && localStorage.setItem(
r,
`${localStorage.getItem(r) ?? ""},${x.join(",")}`
);
}));
let e = localStorage.getItem(r) ?? "";
for (const t of e.split(","))
t.length !== 0 && await z(m(`${U}/${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) && (D(), await Q()));
})();
function A() {
const r = `${Math.random().toString().slice(2)}-${Date.now()}`;
return x.push(r), m(`${U}/${r}`);
}
function $(r, e) {
let t = m(r), a = 0, i = t.createWriter(), n = t.createReader();
const c = async (l) => {
const b = await (await n).read(a, { at: Math.round(a * 0.3) });
a = await l.write(b, { at: 0 }), await l.truncate(a);
};
return {
append: async (l) => {
const o = await i;
a += await o.write(l), a >= e && await c(o);
},
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,
m as file,
$ as rollfile,
A as tmpfile,
V as write
};
//# sourceMappingURL=opfs-tools.js.map