Allow mounting other http fs's
Some checks failed
Tests / tests (push) Has been cancelled

This commit is contained in:
Dustin Brett 2025-08-31 15:55:06 -07:00
parent 2141ea7781
commit 66b7a509e8
7 changed files with 82 additions and 12 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ test-results
playwright-report playwright-report
playwright/.cache playwright/.cache
public/private
public/robots.txt public/robots.txt
public/rss.xml public/rss.xml
public/sitemap.xml public/sitemap.xml

View File

@ -108,6 +108,7 @@ const useCommandInterpreter = (
lstat, lstat,
mapFs, mapFs,
mkdirRecursive, mkdirRecursive,
mountHttpRequestFs,
readdir, readdir,
readFile, readFile,
rename, rename,
@ -680,8 +681,27 @@ const useCommandInterpreter = (
} }
} }
break; break;
case "mount": case "mount": {
if (isFileSystemMappingSupported()) { 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 { try {
const mappedFolder = await mapFs(cd.current); const mappedFolder = await mapFs(cd.current);
@ -700,6 +720,7 @@ const useCommandInterpreter = (
printLn(COMMAND_NOT_SUPPORTED); printLn(COMMAND_NOT_SUPPORTED);
} }
break; break;
}
case "move": case "move":
case "mv": case "mv":
case "ren": case "ren":
@ -1255,6 +1276,7 @@ const useCommandInterpreter = (
lstat, lstat,
mapFs, mapFs,
mkdirRecursive, mkdirRecursive,
mountHttpRequestFs,
open, open,
processesRef, processesRef,
readFile, readFile,

View File

@ -8,6 +8,7 @@ import index from "public/.index/fs.9p.json";
import { import {
FS_HANDLES, FS_HANDLES,
MOUNTABLE_EXTENSIONS, MOUNTABLE_EXTENSIONS,
MOUNTABLE_FS_TYPES,
ONE_TIME_PASSIVE_EVENT, ONE_TIME_PASSIVE_EVENT,
} from "utils/constants"; } from "utils/constants";
@ -21,7 +22,7 @@ type FS9PV3 = [
number, number,
FS9PV3[] | string, FS9PV3[] | string,
]; ];
type FS9PV4 = [string, number, number, FS9PV4[] | undefined]; export type FS9PV4 = [string, number, number, FS9PV4[] | undefined];
type FS9P = { type FS9P = {
fsroot: FS9PV3[]; fsroot: FS9PV3[];
size: number; size: number;
@ -76,7 +77,7 @@ export const get9pModifiedTime = (path: string): number =>
export const get9pSize = (path: string): number => get9pData(path, IDX_SIZE); export const get9pSize = (path: string): number => get9pData(path, IDX_SIZE);
const parseDirectory = (array: FS9PV4[]): BFSFS => { export const parseDirectory = (array: FS9PV4[]): BFSFS => {
const directory: BFSFS = {}; const directory: BFSFS = {};
// eslint-disable-next-line unicorn/no-unreadable-array-destructuring // eslint-disable-next-line unicorn/no-unreadable-array-destructuring
@ -201,7 +202,7 @@ export const getFileSystemHandles = async (): Promise<FileSystemHandles> => {
export const isMountedFolder = (mount?: Mount): boolean => export const isMountedFolder = (mount?: Mount): boolean =>
typeof mount === "object" && typeof mount === "object" &&
(mount.getName() === "FileSystemAccess" || (MOUNTABLE_FS_TYPES.has(mount.getName()) ||
(mount as ExtendedEmscriptenFileSystem)._FS?.DB_STORE_NAME === "FILE_DATA"); (mount as ExtendedEmscriptenFileSystem)._FS?.DB_STORE_NAME === "FILE_DATA");
export const getMountUrl = ( export const getMountUrl = (

View File

@ -22,7 +22,9 @@ import {
getFileSystemHandles, getFileSystemHandles,
hasIndexedDB, hasIndexedDB,
isMountedFolder, isMountedFolder,
parseDirectory,
KEYVAL_DB, KEYVAL_DB,
type FS9PV4,
} from "contexts/fileSystem/core"; } from "contexts/fileSystem/core";
import useAsyncFs, { import useAsyncFs, {
type AsyncFS, type AsyncFS,
@ -103,6 +105,11 @@ type FileSystemContextState = AsyncFS & {
mkdirRecursive: (path: string) => Promise<void>; mkdirRecursive: (path: string) => Promise<void>;
mountEmscriptenFs: (FS: EmscriptenFS, fsName?: string) => Promise<string>; mountEmscriptenFs: (FS: EmscriptenFS, fsName?: string) => Promise<string>;
mountFs: (url: string) => Promise<void>; mountFs: (url: string) => Promise<void>;
mountHttpRequestFs: (
mountPoint: string,
url: string,
baseUrl?: string
) => Promise<void>;
moveEntries: (entries: string[]) => void; moveEntries: (entries: string[]) => void;
pasteList: FilePasteOperations; pasteList: FilePasteOperations;
removeFsWatcher: (folder: string, updateFiles: UpdateFiles) => void; removeFsWatcher: (folder: string, updateFiles: UpdateFiles) => void;
@ -293,6 +300,40 @@ const useFileSystemContextState = (): FileSystemContextState => {
}), }),
[rootFs] [rootFs]
); );
const mountHttpRequestFs = useCallback(
async (
mountPoint: string,
url: string,
baseUrl?: string
): Promise<void> => {
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( const mapFs = useCallback(
async ( async (
directory: string, directory: string,
@ -658,6 +699,7 @@ const useFileSystemContextState = (): FileSystemContextState => {
mkdirRecursive, mkdirRecursive,
mountEmscriptenFs, mountEmscriptenFs,
mountFs, mountFs,
mountHttpRequestFs,
moveEntries, moveEntries,
pasteList, pasteList,
removeFsWatcher, removeFsWatcher,

View File

@ -14,9 +14,10 @@
"scripts": { "scripts": {
"build": "yarn build:prebuild && next build", "build": "yarn build:prebuild && next build",
"build:bundle-analyzer": "yarn 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: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:build": "docker build -t daedalos .",
"docker:run": "docker run -dp 3000:3000 --rm --name daedalos daedalos", "docker:run": "docker run -dp 3000:3000 --rm --name daedalos daedalos",
"dev": "next dev", "dev": "next dev",

View File

@ -10,11 +10,11 @@ const IDX_TARGET = 3;
const args = process.argv.slice(2); const args = process.argv.slice(2);
const argPath = resolvePath(args[args.length - 1]); const argPath = resolvePath(args[args.length - 1]);
const excludedPaths = []; let excludedPaths = [];
let outputPath = ""; let outputPath = "";
args.forEach((arg, index) => { 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]); if (arg === "--out") outputPath = resolvePath(args[index + 1]);
}); });
@ -43,9 +43,10 @@ const fs2json = (dir) => {
return; return;
} }
const includedFiles = files.filter( const includedFiles =
(file) => !excludedPaths.includes(file) dir === walkDir
); ? files.filter((file) => !excludedPaths.includes(file))
: files;
const recur = () => { const recur = () => {
const file = includedFiles.shift(); const file = includedFiles.shift();

View File

@ -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_EXTENSIONS = new Set([".iso", ...ZIP_EXTENSIONS]);
export const MOUNTABLE_FS_TYPES = new Set(["FileSystemAccess", "HTTPRequest"]);
export const SPREADSHEET_FORMATS = [ export const SPREADSHEET_FORMATS = [
".csv", ".csv",
".numbers", ".numbers",