browserfs
Version:
A filesystem in your browser!
176 lines (163 loc) • 5.25 kB
text/typescript
import {BaseFileSystem, FileSystem, BFSCallback, FileSystemOptions} from '../core/file_system';
import * as path from 'path';
import {ApiError} from '../core/api_error';
/**
* Configuration options for a FolderAdapter file system.
*/
export interface FolderAdapterOptions {
// The folder to use as the root directory.
folder: string;
// The file system to wrap.
wrapped: FileSystem;
}
/**
* The FolderAdapter file system wraps a file system, and scopes all interactions to a subfolder of that file system.
*
* Example: Given a file system `foo` with folder `bar` and file `bar/baz`...
*
* ```javascript
* BrowserFS.configure({
* fs: "FolderAdapter",
* options: {
* folder: "bar",
* wrapped: foo
* }
* }, function(e) {
* var fs = BrowserFS.BFSRequire('fs');
* fs.readdirSync('/'); // ['baz']
* });
* ```
*/
export default class FolderAdapter extends BaseFileSystem implements FileSystem {
public static readonly Name = "FolderAdapter";
public static readonly Options: FileSystemOptions = {
folder: {
type: "string",
description: "The folder to use as the root directory"
},
wrapped: {
type: "object",
description: "The file system to wrap"
}
};
/**
* Creates a FolderAdapter instance with the given options.
*/
public static Create(opts: FolderAdapterOptions, cb: BFSCallback<FolderAdapter>): void {
cb(null, new FolderAdapter(opts.folder, opts.wrapped));
}
public static isAvailable(): boolean {
return true;
}
public _wrapped: FileSystem;
public _folder: string;
/**
* Wraps a file system, and uses the given folder as its root.
*
* @param folder The folder to use as the root directory.
* @param wrapped The file system to wrap.
*/
constructor(folder: string, wrapped: FileSystem) {
super();
this._folder = folder;
this._wrapped = wrapped;
}
/**
* Initialize the file system. Ensures that the wrapped file system
* has the given folder.
*/
public initialize(cb: (e?: ApiError) => void) {
this._wrapped.exists(this._folder, (exists: boolean) => {
if (exists) {
cb();
} else if (this._wrapped.isReadOnly()) {
cb(ApiError.ENOENT(this._folder));
} else {
this._wrapped.mkdir(this._folder, 0x1ff, cb);
}
});
}
public getName(): string { return this._wrapped.getName(); }
public isReadOnly(): boolean { return this._wrapped.isReadOnly(); }
public supportsProps(): boolean { return this._wrapped.supportsProps(); }
public supportsSynch(): boolean { return this._wrapped.supportsSynch(); }
public supportsLinks(): boolean { return false; }
}
/**
* @hidden
*/
function translateError(folder: string, e: any): any {
if (e !== null && typeof e === 'object') {
const err = <ApiError> e;
let p = err.path;
if (p) {
p = '/' + path.relative(folder, p);
err.message = err.message.replace(err.path!, p);
err.path = p;
}
}
return e;
}
/**
* @hidden
*/
function wrapCallback(folder: string, cb: any): any {
if (typeof cb === 'function') {
return function(err: ApiError) {
if (arguments.length > 0) {
arguments[0] = translateError(folder, err);
}
(<Function> cb).apply(null, arguments);
};
} else {
return cb;
}
}
/**
* @hidden
*/
function wrapFunction(name: string, wrapFirst: boolean, wrapSecond: boolean): Function {
if (name.slice(name.length - 4) !== 'Sync') {
// Async function. Translate error in callback.
return function(this: FolderAdapter) {
if (arguments.length > 0) {
if (wrapFirst) {
arguments[0] = path.join(this._folder, arguments[0]);
}
if (wrapSecond) {
arguments[1] = path.join(this._folder, arguments[1]);
}
arguments[arguments.length - 1] = wrapCallback(this._folder, arguments[arguments.length - 1]);
}
return (<any> this._wrapped)[name].apply(this._wrapped, arguments);
};
} else {
// Sync function. Translate error in catch.
return function(this: FolderAdapter) {
try {
if (wrapFirst) {
arguments[0] = path.join(this._folder, arguments[0]);
}
if (wrapSecond) {
arguments[1] = path.join(this._folder, arguments[1]);
}
return (<any> this._wrapped)[name].apply(this._wrapped, arguments);
} catch (e) {
throw translateError(this._folder, e);
}
};
}
}
// First argument is a path.
['diskSpace', 'stat', 'statSync', 'open', 'openSync', 'unlink', 'unlinkSync',
'rmdir', 'rmdirSync', 'mkdir', 'mkdirSync', 'readdir', 'readdirSync', 'exists',
'existsSync', 'realpath', 'realpathSync', 'truncate', 'truncateSync', 'readFile',
'readFileSync', 'writeFile', 'writeFileSync', 'appendFile', 'appendFileSync',
'chmod', 'chmodSync', 'chown', 'chownSync', 'utimes', 'utimesSync', 'readlink',
'readlinkSync'].forEach((name: string) => {
(<any> FolderAdapter.prototype)[name] = wrapFunction(name, true, false);
});
// First and second arguments are paths.
['rename', 'renameSync', 'link', 'linkSync', 'symlink', 'symlinkSync'].forEach((name: string) => {
(<any> FolderAdapter.prototype)[name] = wrapFunction(name, true, true);
});