diff --git a/src/backends/HTTPRequest.ts b/src/backends/HTTPRequest.ts index 964974f..d2c3d0b 100644 --- a/src/backends/HTTPRequest.ts +++ b/src/backends/HTTPRequest.ts @@ -4,7 +4,7 @@ import { copyingSlice } from '@browserfs/core/utils.js'; import { File, FileFlag, ActionType, NoSyncFile } from '@browserfs/core/file.js'; import { Stats } from '@browserfs/core/stats.js'; import { fetchIsAvailable, fetchFile, fetchFileSize } from '../fetch.js'; -import { FileIndex, isFileInode, isDirInode } from '../file_index.js'; +import { FileIndex, isIndexFileInode, isIndexDirInode } from '@browserfs/core/FileIndex.js'; import { Cred } from '@browserfs/core/cred.js'; import { CreateBackend, type BackendOptions } from '@browserfs/core/backends/backend.js'; import { R_OK } from '@browserfs/core/emulation/constants.js'; @@ -129,7 +129,7 @@ export class HTTPRequest extends BaseFileSystem { */ public preloadFile(path: string, buffer: Buffer): void { const inode = this._index.getInode(path); - if (isFileInode(inode)) { + if (isIndexFileInode(inode)) { if (inode === null) { throw ApiError.ENOENT(path); } @@ -150,13 +150,13 @@ export class HTTPRequest extends BaseFileSystem { throw ApiError.EACCES(path); } let stats: Stats; - if (isFileInode(inode)) { + if (isIndexFileInode(inode)) { stats = inode.getData(); // At this point, a non-opened file will still have default stats from the listing. if (stats.size < 0) { stats.size = await this._requestFileSize(path); } - } else if (isDirInode(inode)) { + } else if (isIndexDirInode(inode)) { stats = inode.getStats(); } else { throw ApiError.FileError(ErrorCode.EINVAL, path); @@ -177,13 +177,13 @@ export class HTTPRequest extends BaseFileSystem { if (!inode.toStats().hasAccess(flags.getMode(), cred)) { throw ApiError.EACCES(path); } - if (isFileInode(inode) || isDirInode(inode)) { + if (isIndexFileInode(inode) || isIndexDirInode(inode)) { switch (flags.pathExistsAction()) { case ActionType.THROW_EXCEPTION: case ActionType.TRUNCATE_FILE: throw ApiError.EEXIST(path); case ActionType.NOP: - if (isDirInode(inode)) { + if (isIndexDirInode(inode)) { const stats = inode.getStats(); return new NoSyncFile(this, path, flags, stats, stats.fileData || undefined); } diff --git a/src/file_index.ts b/src/file_index.ts deleted file mode 100644 index 52687fe..0000000 --- a/src/file_index.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { Stats, FileType } from '@browserfs/core/stats.js'; -import * as path from 'path'; - -/** - * A simple class for storing a filesystem index. Assumes that all paths passed - * to it are *absolute* paths. - * - * Can be used as a partial or a full index, although care must be taken if used - * for the former purpose, especially when directories are concerned. - */ -export class FileIndex { - /** - * Static method for constructing indices from a JSON listing. - * @param listing Directory listing generated by tools/XHRIndexer.coffee - * @return A new FileIndex object. - */ - public static fromListing(listing: any): FileIndex { - const idx = new FileIndex(); - // Add a root DirNode. - const rootInode = new DirInode(); - idx._index['/'] = rootInode; - const queue = [['', listing, rootInode]]; - while (queue.length > 0) { - let inode: Inode; - const next = queue.pop(); - const pwd = next![0]; - const tree = next![1]; - const parent = next![2]; - for (const node in tree) { - if (Object.prototype.hasOwnProperty.call(tree, node)) { - const children = tree[node]; - const name = `${pwd}/${node}`; - if (children) { - idx._index[name] = inode = new DirInode(); - queue.push([name, children, inode]); - } else { - // This inode doesn't have correct size information, noted with -1. - inode = new FileInode(new Stats(FileType.FILE, -1, 0x16d)); - } - if (parent) { - parent._ls[node] = inode; - } - } - } - } - return idx; - } - - // Maps directory paths to directory inodes, which contain files. - private _index: { [path: string]: DirInode }; - - /** - * Constructs a new FileIndex. - */ - constructor() { - // _index is a single-level key,value store that maps *directory* paths to - // DirInodes. File information is only contained in DirInodes themselves. - this._index = {}; - // Create the root directory. - this.addPath('/', new DirInode()); - } - - /** - * Runs the given function over all files in the index. - */ - public fileIterator(cb: (file: T | null) => void): void { - for (const path in this._index) { - if (Object.prototype.hasOwnProperty.call(this._index, path)) { - const dir = this._index[path]; - const files = dir.getListing(); - for (const file of files) { - const item = dir.getItem(file); - if (isFileInode(item)) { - cb(item.getData()); - } - } - } - } - } - - /** - * Adds the given absolute path to the index if it is not already in the index. - * Creates any needed parent directories. - * @param path The path to add to the index. - * @param inode The inode for the - * path to add. - * @return 'True' if it was added or already exists, 'false' if there - * was an issue adding it (e.g. item in path is a file, item exists but is - * different). - * @todo If adding fails and implicitly creates directories, we do not clean up - * the new empty directories. - */ - public addPath(path: string, inode: Inode): boolean { - if (!inode) { - throw new Error('Inode must be specified'); - } - if (path[0] !== '/') { - throw new Error('Path must be absolute, got: ' + path); - } - - // Check if it already exists. - if (Object.prototype.hasOwnProperty.call(this._index, path)) { - return this._index[path] === inode; - } - - const splitPath = this._split_path(path); - const dirpath = splitPath[0]; - const itemname = splitPath[1]; - // Try to add to its parent directory first. - let parent = this._index[dirpath]; - if (parent === undefined && path !== '/') { - // Create parent. - parent = new DirInode(); - if (!this.addPath(dirpath, parent)) { - return false; - } - } - // Add myself to my parent. - if (path !== '/') { - if (!parent.addItem(itemname, inode)) { - return false; - } - } - // If I'm a directory, add myself to the index. - if (isDirInode(inode)) { - this._index[path] = inode; - } - return true; - } - - /** - * Adds the given absolute path to the index if it is not already in the index. - * The path is added without special treatment (no joining of adjacent separators, etc). - * Creates any needed parent directories. - * @param path The path to add to the index. - * @param inode The inode for the - * path to add. - * @return 'True' if it was added or already exists, 'false' if there - * was an issue adding it (e.g. item in path is a file, item exists but is - * different). - * @todo If adding fails and implicitly creates directories, we do not clean up - * the new empty directories. - */ - public addPathFast(path: string, inode: Inode): boolean { - const itemNameMark = path.lastIndexOf('/'); - const parentPath = itemNameMark === 0 ? '/' : path.substring(0, itemNameMark); - const itemName = path.substring(itemNameMark + 1); - - // Try to add to its parent directory first. - let parent = this._index[parentPath]; - if (parent === undefined) { - // Create parent. - parent = new DirInode(); - this.addPathFast(parentPath, parent); - } - - if (!parent.addItem(itemName, inode)) { - return false; - } - - // If adding a directory, add to the index as well. - if (inode.isDir()) { - this._index[path] = >inode; - } - return true; - } - - /** - * Removes the given path. Can be a file or a directory. - * @return The removed item, - * or null if it did not exist. - */ - public removePath(path: string): Inode | null { - const splitPath = this._split_path(path); - const dirpath = splitPath[0]; - const itemname = splitPath[1]; - - // Try to remove it from its parent directory first. - const parent = this._index[dirpath]; - if (parent === undefined) { - return null; - } - // Remove myself from my parent. - const inode = parent.remItem(itemname); - if (inode === null) { - return null; - } - // If I'm a directory, remove myself from the index, and remove my children. - if (isDirInode(inode)) { - const children = inode.getListing(); - for (const child of children) { - this.removePath(path + '/' + child); - } - - // Remove the directory from the index, unless it's the root. - if (path !== '/') { - delete this._index[path]; - } - } - return inode; - } - - /** - * Retrieves the directory listing of the given path. - * @return An array of files in the given path, or 'null' if it does not exist. - */ - public ls(path: string): string[] | null { - const item = this._index[path]; - if (item === undefined) { - return null; - } - return item.getListing(); - } - - /** - * Returns the inode of the given item. - * @return Returns null if the item does not exist. - */ - public getInode(path: string): Inode | null { - const splitPath = this._split_path(path); - const dirpath = splitPath[0]; - const itemname = splitPath[1]; - // Retrieve from its parent directory. - const parent = this._index[dirpath]; - if (parent === undefined) { - return null; - } - // Root case - if (dirpath === path) { - return parent; - } - return parent.getItem(itemname); - } - - /** - * Split into a (directory path, item name) pair - */ - private _split_path(p: string): string[] { - const dirpath = path.dirname(p); - const itemname = p.substr(dirpath.length + (dirpath === '/' ? 0 : 1)); - return [dirpath, itemname]; - } -} - -/** - * Generic interface for file/directory inodes. - * Note that Stats objects are what we use for file inodes. - */ -export interface Inode { - // Is this an inode for a file? - isFile(): boolean; - // Is this an inode for a directory? - isDir(): boolean; - //compatibility with other Inode types - toStats(): Stats; -} - -/** - * Inode for a file. Stores an arbitrary (filesystem-specific) data payload. - */ -export class FileInode implements Inode { - constructor(private data: T) {} - public isFile(): boolean { - return true; - } - public isDir(): boolean { - return false; - } - public getData(): T { - return this.data; - } - public setData(data: T): void { - this.data = data; - } - public toStats(): Stats { - return new Stats(FileType.FILE, 4096, 0o666); - } -} - -/** - * Inode for a directory. Currently only contains the directory listing. - */ -export class DirInode implements Inode { - private _ls: { [path: string]: Inode } = {}; - /** - * Constructs an inode for a directory. - */ - constructor(private data: T | null = null) {} - public isFile(): boolean { - return false; - } - public isDir(): boolean { - return true; - } - public getData(): T | null { - return this.data; - } - - /** - * Return a Stats object for this inode. - * @todo Should probably remove this at some point. This isn't the - * responsibility of the FileIndex. - */ - public getStats(): Stats { - return new Stats(FileType.DIRECTORY, 4096, 0o555); - } - /** - * Alias of getStats() - * @todo Remove this at some point. This isn't the - * responsibility of the FileIndex. - */ - public toStats(): Stats { - return this.getStats(); - } - /** - * Returns the directory listing for this directory. Paths in the directory are - * relative to the directory's path. - * @return The directory listing for this directory. - */ - public getListing(): string[] { - return Object.keys(this._ls); - } - /** - * Returns the inode for the indicated item, or null if it does not exist. - * @param p Name of item in this directory. - */ - public getItem(p: string): Inode | null { - const item = this._ls[p]; - return item ? item : null; - } - /** - * Add the given item to the directory listing. Note that the given inode is - * not copied, and will be mutated by the DirInode if it is a DirInode. - * @param p Item name to add to the directory listing. - * @param inode The inode for the - * item to add to the directory inode. - * @return True if it was added, false if it already existed. - */ - public addItem(p: string, inode: Inode): boolean { - if (p in this._ls) { - return false; - } - this._ls[p] = inode; - return true; - } - /** - * Removes the given item from the directory listing. - * @param p Name of item to remove from the directory listing. - * @return Returns the item - * removed, or null if the item did not exist. - */ - public remItem(p: string): Inode | null { - const item = this._ls[p]; - if (item === undefined) { - return null; - } - delete this._ls[p]; - return item; - } -} - -/** - * @hidden - */ -export function isFileInode(inode: Inode | null): inode is FileInode { - return !!inode && inode.isFile(); -} - -/** - * @hidden - */ -export function isDirInode(inode: Inode | null): inode is DirInode { - return !!inode && inode.isDir(); -}