From 66b7a509e85c08cb69d27ad5195c83f612596b1d Mon Sep 17 00:00:00 2001 From: Dustin Brett Date: Sun, 31 Aug 2025 15:55:06 -0700 Subject: [PATCH] Allow mounting other http fs's --- .gitignore | 1 + .../apps/Terminal/useCommandInterpreter.ts | 26 +++++++++++- contexts/fileSystem/core.ts | 7 ++-- .../fileSystem/useFileSystemContextState.ts | 42 +++++++++++++++++++ package.json | 5 ++- scripts/fs2json.js | 11 ++--- utils/constants.ts | 2 + 7 files changed, 82 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 6dc4a3b5..8c683cd2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ test-results playwright-report playwright/.cache +public/private public/robots.txt public/rss.xml public/sitemap.xml diff --git a/components/apps/Terminal/useCommandInterpreter.ts b/components/apps/Terminal/useCommandInterpreter.ts index 4f96f9ac..22ac28d6 100644 --- a/components/apps/Terminal/useCommandInterpreter.ts +++ b/components/apps/Terminal/useCommandInterpreter.ts @@ -108,6 +108,7 @@ const useCommandInterpreter = ( lstat, mapFs, mkdirRecursive, + mountHttpRequestFs, readdir, readFile, rename, @@ -680,8 +681,27 @@ const useCommandInterpreter = ( } } break; - case "mount": - if (isFileSystemMappingSupported()) { + case "mount": { + const [mountPoint, url, baseUrl] = commandArgs; + + if (mountPoint && url) { + try { + await mountHttpRequestFs( + mountPoint, + url, + baseUrl === "/" ? undefined : baseUrl || mountPoint + ); + + const basePath = dirname(mountPoint); + + updateFolder( + basePath === "." ? "/" : basePath, + basename(mountPoint) + ); + } catch (error) { + printLn(error); + } + } else if (isFileSystemMappingSupported()) { try { const mappedFolder = await mapFs(cd.current); @@ -700,6 +720,7 @@ const useCommandInterpreter = ( printLn(COMMAND_NOT_SUPPORTED); } break; + } case "move": case "mv": case "ren": @@ -1255,6 +1276,7 @@ const useCommandInterpreter = ( lstat, mapFs, mkdirRecursive, + mountHttpRequestFs, open, processesRef, readFile, diff --git a/contexts/fileSystem/core.ts b/contexts/fileSystem/core.ts index 8e1cbc30..9970003d 100644 --- a/contexts/fileSystem/core.ts +++ b/contexts/fileSystem/core.ts @@ -8,6 +8,7 @@ import index from "public/.index/fs.9p.json"; import { FS_HANDLES, MOUNTABLE_EXTENSIONS, + MOUNTABLE_FS_TYPES, ONE_TIME_PASSIVE_EVENT, } from "utils/constants"; @@ -21,7 +22,7 @@ type FS9PV3 = [ number, FS9PV3[] | string, ]; -type FS9PV4 = [string, number, number, FS9PV4[] | undefined]; +export type FS9PV4 = [string, number, number, FS9PV4[] | undefined]; type FS9P = { fsroot: FS9PV3[]; size: number; @@ -76,7 +77,7 @@ export const get9pModifiedTime = (path: string): number => export const get9pSize = (path: string): number => get9pData(path, IDX_SIZE); -const parseDirectory = (array: FS9PV4[]): BFSFS => { +export const parseDirectory = (array: FS9PV4[]): BFSFS => { const directory: BFSFS = {}; // eslint-disable-next-line unicorn/no-unreadable-array-destructuring @@ -201,7 +202,7 @@ export const getFileSystemHandles = async (): Promise => { export const isMountedFolder = (mount?: Mount): boolean => typeof mount === "object" && - (mount.getName() === "FileSystemAccess" || + (MOUNTABLE_FS_TYPES.has(mount.getName()) || (mount as ExtendedEmscriptenFileSystem)._FS?.DB_STORE_NAME === "FILE_DATA"); export const getMountUrl = ( diff --git a/contexts/fileSystem/useFileSystemContextState.ts b/contexts/fileSystem/useFileSystemContextState.ts index 7717ebba..8a31c202 100644 --- a/contexts/fileSystem/useFileSystemContextState.ts +++ b/contexts/fileSystem/useFileSystemContextState.ts @@ -22,7 +22,9 @@ import { getFileSystemHandles, hasIndexedDB, isMountedFolder, + parseDirectory, KEYVAL_DB, + type FS9PV4, } from "contexts/fileSystem/core"; import useAsyncFs, { type AsyncFS, @@ -103,6 +105,11 @@ type FileSystemContextState = AsyncFS & { mkdirRecursive: (path: string) => Promise; mountEmscriptenFs: (FS: EmscriptenFS, fsName?: string) => Promise; mountFs: (url: string) => Promise; + mountHttpRequestFs: ( + mountPoint: string, + url: string, + baseUrl?: string + ) => Promise; moveEntries: (entries: string[]) => void; pasteList: FilePasteOperations; removeFsWatcher: (folder: string, updateFiles: UpdateFiles) => void; @@ -293,6 +300,40 @@ const useFileSystemContextState = (): FileSystemContextState => { }), [rootFs] ); + const mountHttpRequestFs = useCallback( + async ( + mountPoint: string, + url: string, + baseUrl?: string + ): Promise => { + const index = (await (await fetch(url)).json()) as object; + + if (!(typeof index === "object" && "fsroot" in index)) { + throw new Error("Invalid HTTPRequest FS object."); + } + + const { + FileSystem: { HTTPRequest }, + } = (await import( + "public/System/BrowserFS/browserfs.min.js" + )) as typeof IBrowserFS; + + return new Promise((resolve, reject) => { + HTTPRequest?.Create( + { baseUrl, index: parseDirectory(index.fsroot as FS9PV4[]) }, + (error, newFs) => { + if (error || !newFs) { + reject(new Error("Error while mounting HTTPRequest FS.")); + } else { + rootFs?.mount?.(mountPoint, newFs); + resolve(); + } + } + ); + }); + }, + [rootFs] + ); const mapFs = useCallback( async ( directory: string, @@ -658,6 +699,7 @@ const useFileSystemContextState = (): FileSystemContextState => { mkdirRecursive, mountEmscriptenFs, mountFs, + mountHttpRequestFs, moveEntries, pasteList, removeFsWatcher, diff --git a/package.json b/package.json index 643f625a..29dc264f 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,10 @@ "scripts": { "build": "yarn build:prebuild && next build", "build:bundle-analyzer": "yarn build", - "build:fs": "node scripts/fs2json.js --exclude .index --out public/.index/fs.9p.json ./public", + "build:fs:private": "node scripts/fs2json.js --out public/.index/fs.private.9p.json ./public/private", + "build:fs:public": "node scripts/fs2json.js --exclude .index,private --out public/.index/fs.9p.json ./public", "build:minify": "node scripts/minifyHtml.js && node scripts/minifyJs.js", - "build:prebuild": "node scripts/robots.js && node scripts/rssBuilder.js && node scripts/searchIndex.js && node scripts/preloadIcons.js && node scripts/cacheShortcuts.js && yarn build:fs", + "build:prebuild": "node scripts/robots.js && node scripts/rssBuilder.js && node scripts/searchIndex.js && node scripts/preloadIcons.js && node scripts/cacheShortcuts.js && yarn build:fs:public", "docker:build": "docker build -t daedalos .", "docker:run": "docker run -dp 3000:3000 --rm --name daedalos daedalos", "dev": "next dev", diff --git a/scripts/fs2json.js b/scripts/fs2json.js index 46a7b73d..b19b6f91 100644 --- a/scripts/fs2json.js +++ b/scripts/fs2json.js @@ -10,11 +10,11 @@ const IDX_TARGET = 3; const args = process.argv.slice(2); const argPath = resolvePath(args[args.length - 1]); -const excludedPaths = []; +let excludedPaths = []; let outputPath = ""; args.forEach((arg, index) => { - if (arg === "--exclude") excludedPaths.push(args[index + 1]); + if (arg === "--exclude") excludedPaths = args[index + 1].split(","); if (arg === "--out") outputPath = resolvePath(args[index + 1]); }); @@ -43,9 +43,10 @@ const fs2json = (dir) => { return; } - const includedFiles = files.filter( - (file) => !excludedPaths.includes(file) - ); + const includedFiles = + dir === walkDir + ? files.filter((file) => !excludedPaths.includes(file)) + : files; const recur = () => { const file = includedFiles.shift(); diff --git a/utils/constants.ts b/utils/constants.ts index 9d7efae2..5bb84e74 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -173,6 +173,8 @@ export const ZIP_EXTENSIONS = new Set([".jsdos", ".pk3", ".wsz", ".zip"]); export const MOUNTABLE_EXTENSIONS = new Set([".iso", ...ZIP_EXTENSIONS]); +export const MOUNTABLE_FS_TYPES = new Set(["FileSystemAccess", "HTTPRequest"]); + export const SPREADSHEET_FORMATS = [ ".csv", ".numbers",