1
0
silverbullet/plugos/syscalls/fs.deno.ts
2023-11-21 12:05:17 +01:00

108 lines
3.4 KiB
TypeScript

import type { SysCallMapping } from "../system.ts";
import { mime, path, walk } from "../deps.ts";
import { base64DecodeDataUrl, base64Encode } from "../asset_bundle/base64.ts";
import { FileMeta } from "$sb/types.ts";
export default function fileSystemSyscalls(root = "/"): SysCallMapping {
function resolvedPath(p: string): string {
p = path.resolve(root, p);
if (!p.startsWith(root)) {
throw Error("Path outside root, not allowed");
}
return p;
}
return {
"fs.readFile": async (
_ctx,
filePath: string,
encoding: "utf8" | "dataurl" = "utf8",
): Promise<string> => {
const p = resolvedPath(filePath);
let text = "";
if (encoding === "utf8") {
text = await Deno.readTextFile(p);
} else {
text = `data:application/octet-stream,${
base64Encode(await Deno.readFile(p))
}`;
}
return text;
},
"fs.getFileMeta": async (_ctx, filePath: string): Promise<FileMeta> => {
const p = resolvedPath(filePath);
const s = await Deno.stat(p);
return {
name: filePath,
created: s.birthtime?.getTime() || s.mtime?.getTime() || 0,
lastModified: s.mtime?.getTime() || 0,
contentType: mime.getType(filePath) || "application/octet-stream",
size: s.size,
perm: "rw",
};
},
"fs.writeFile": async (
_ctx,
filePath: string,
text: string,
encoding: "utf8" | "dataurl" = "utf8",
): Promise<FileMeta> => {
const p = resolvedPath(filePath);
await Deno.mkdir(path.dirname(p), { recursive: true });
if (encoding === "utf8") {
await Deno.writeTextFile(p, text);
} else {
await Deno.writeFile(p, base64DecodeDataUrl(text));
}
const s = await Deno.stat(p);
return {
name: filePath,
created: s.birthtime?.getTime() || s.mtime?.getTime() || 0,
lastModified: s.mtime?.getTime() || 0,
contentType: mime.getType(filePath) || "application/octet-stream",
size: s.size,
perm: "rw",
};
},
"fs.deleteFile": async (_ctx, filePath: string): Promise<void> => {
await Deno.remove(resolvedPath(filePath));
},
"fs.listFiles": async (
_ctx,
dirPath: string,
recursive: boolean,
): Promise<FileMeta[]> => {
dirPath = resolvedPath(dirPath);
const allFiles: FileMeta[] = [];
for await (
const file of walk(dirPath, {
includeDirs: false,
// Exclude hidden files
skip: [
// Dynamically builds a regexp that matches hidden directories INSIDE the rootPath
// (but if the rootPath is hidden, it stil lists files inside of it, fixing #130 and #518)
new RegExp(`^${escapeRegExp(root)}.*\\/\\..+$`),
],
maxDepth: recursive ? Infinity : 1,
})
) {
const fullPath = file.path;
const s = await Deno.stat(fullPath);
allFiles.push({
name: fullPath.substring(dirPath.length + 1),
created: s.birthtime?.getTime() || s.mtime?.getTime() || 0,
lastModified: s.mtime?.getTime() || 0,
contentType: mime.getType(fullPath) || "application/octet-stream",
size: s.size,
perm: "rw",
});
}
return allFiles;
},
};
}
function escapeRegExp(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}