From 9af4bcd46c3bfb80d5f25d1f9324e46547fe3409 Mon Sep 17 00:00:00 2001 From: Will Hilton Date: Tue, 22 Aug 2017 22:16:04 -0400 Subject: [PATCH 1/4] Fix linter whitespace errors --- src/core/FS.ts | 2 +- src/core/levenshtein.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/core/FS.ts b/src/core/FS.ts index 8de3baa3..5120e7a5 100644 --- a/src/core/FS.ts +++ b/src/core/FS.ts @@ -419,7 +419,7 @@ export default class FS { public readFile(filename: string, options: { flag?: string; }, callback?: BFSCallback): void; public readFile(filename: string, options: { encoding: string; flag?: string; }, callback?: BFSCallback): void; public readFile(filename: string, encoding: string, cb: BFSCallback): void; - public readFile(filename: string, arg2: any = {}, cb: BFSCallback = nopCb ) { + public readFile(filename: string, arg2: any = {}, cb: BFSCallback = nopCb) { const options = normalizeOptions(arg2, null, 'r', null); cb = typeof arg2 === 'function' ? arg2 : cb; const newCb = wrapCb(cb, 2); diff --git a/src/core/levenshtein.ts b/src/core/levenshtein.ts index 2a5c80e0..f52fb2c3 100644 --- a/src/core/levenshtein.ts +++ b/src/core/levenshtein.ts @@ -52,7 +52,9 @@ export default function levenshtein(a: string, b: string): number { const vector = new Array(la << 1); + /* tslint:disable:space-within-parens */ for (let y = 0; y < la; ) { + /* tslint:enable:space-within-parens */ vector[la + y] = a.charCodeAt(offset + y); vector[y] = ++y; } @@ -62,13 +64,17 @@ export default function levenshtein(a: string, b: string): number { let d1: number; let d2: number; let d3: number; + /* tslint:disable:space-within-parens */ for (x = 0; (x + 3) < lb; ) { + /* tslint:enable:space-within-parens */ const bx0 = b.charCodeAt(offset + (d0 = x)); const bx1 = b.charCodeAt(offset + (d1 = x + 1)); const bx2 = b.charCodeAt(offset + (d2 = x + 2)); const bx3 = b.charCodeAt(offset + (d3 = x + 3)); let dd = (x += 4); + /* tslint:disable:space-within-parens */ for (let y = 0; y < la; ) { + /* tslint:enable:space-within-parens */ const ay = vector[la + y]; const dy = vector[y]; d0 = _min(dy, d0, d1, bx0, ay); @@ -84,7 +90,9 @@ export default function levenshtein(a: string, b: string): number { } let dd: number = 0; + /* tslint:disable:space-within-parens */ for (; x < lb; ) { + /* tslint:enable:space-within-parens */ const bx0 = b.charCodeAt(offset + (d0 = x)); dd = ++x; for (let y = 0; y < la; y++) { From 1e8404ac9e14ecb1e25dc51427a0838addf6b0e7 Mon Sep 17 00:00:00 2001 From: Will Hilton Date: Tue, 22 Aug 2017 22:35:17 -0400 Subject: [PATCH 2/4] Start implementing a middleware/hook FS --- src/backend/Middleware.ts | 286 +++++++++++++++++++ src/core/backends.ts | 5 +- test/harness/factories/middleware_factory.ts | 23 ++ 3 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 src/backend/Middleware.ts create mode 100644 test/harness/factories/middleware_factory.ts diff --git a/src/backend/Middleware.ts b/src/backend/Middleware.ts new file mode 100644 index 00000000..06ed4b88 --- /dev/null +++ b/src/backend/Middleware.ts @@ -0,0 +1,286 @@ +import Stats from '../core/node_fs_stats'; +import {File} from '../core/file'; +import {FileFlag} from '../core/file_flag'; +import {FileSystem, BFSCallback, BFSOneArgCallback, FileSystemOptions} from '../core/file_system'; +// import * as path from 'path'; +// import {ApiError} from '../core/api_error'; + +/** + * Configuration options for a Middleware file system. + */ +export interface MiddlewareOptions { + // The file system to wrap. + wrapped: FileSystem; + beforeRead?: Function; + afterRead?: Function; + beforeWrite?: Function; + afterWrite?: Function; + readTransformer?: Function; // (this, fname, enc, etc) => you implement + writeTransformer?: Function; // (this, fname, enc, etc) => you implement +} + +function noop(args: any, cb?: BFSOneArgCallback) { + if (cb) { + return cb(null); + } + return true; +} + +/** + * The Middleware file system wraps a file system, and intercepts all reads and writes + * so you can modify their behavior. + * + * Example: Given a file system `foo`... + * + * ```javascript + * const BrowserFS = require('.') + * BrowserFS.configure({ + * fs: "Middleware", + * options: { + * wrapped: { + * fs: "InMemory", + * options: {} + * }, + * beforeWrite: ({data}) => console.log('about to write ' + data), + * afterWrite: ({fname}) => console.log('just wrote ' + fname), + * beforeRead: ({fname}) => console.log('about to read ' + fname), + * afterRead: ({data}) => console.log('just read ' + data), + * } + * }, (err) => { + * if (err) throw err; + * const fs = BrowserFS.BFSRequire('fs') + * fs.writeFileSync('hello', 'hello world', 'utf8'); + * fs.readFileSync('hello', 'utf8'); + * }) + * + * //// Should print to console the following: + * // about to write hello world + * // just wrote /hello + * // about to read /hello + * // just read hello world + * ``` + * + */ +export default class Middleware implements FileSystem { + public static readonly Name = "Middleware"; + + public static readonly Options: FileSystemOptions = { + wrapped: { + type: "object", + description: "The file system to wrap", + optional: false + }, + beforeRead: { + type: "function", + description: "Called before a file is read.", + optional: true + }, + afterRead: { + type: "function", + description: "Called after a file is read.", + optional: true + }, + beforeWrite: { + type: "function", + description: "Called before a file is written.", + optional: true + }, + afterWrite: { + type: "function", + description: "Called after a file is written.", + optional: true + }, + readTransformer: { + type: "function", + description: "Intercept and handle all reads manually.", + optional: true + }, + writeTransformer: { + type: "function", + description: "Intercept and handle all write manually.", + optional: true + }, + }; + + /** + * Creates a Middleware instance with the given options. + */ + public static Create(opts: MiddlewareOptions, cb: BFSCallback): void { + try { + const fs = new Middleware( + opts.wrapped, + opts.beforeRead || noop, + opts.afterRead || noop, + opts.beforeWrite || noop, + opts.afterWrite || noop, + opts.readTransformer || noop, + opts.writeTransformer || noop + ); + cb(undefined, fs); + } catch (e) { + cb(e); + } + } + public static isAvailable(): boolean { + return true; + } + + public _wrapped: FileSystem; + private _beforeRead: Function; + private _afterRead: Function; + private _beforeWrite: Function; + private _afterWrite: Function; + private _readTransformer: Function; + private _writeTransformer: Function; + // private _create: Function; + // private _delete: Function; + // private _mkdir: Function; + // private _rmdir: Function; + + private constructor( + wrapped: FileSystem, + beforeRead: Function, + afterRead: Function, + beforeWrite: Function, + afterWrite: Function, + readTransformer: Function, + writeTransformer: Function + ) { + this._wrapped = wrapped; + this._beforeRead = beforeRead; + this._afterRead = afterRead; + this._beforeWrite = beforeWrite; + this._afterWrite = afterWrite; + this._readTransformer = readTransformer; + this._writeTransformer = writeTransformer; + } + + public getName(): string { return this._wrapped.getName(); } + public diskSpace(p: string, cb: (total: number, free: number) => any): void { + return this._wrapped.diskSpace(p, cb); + } + public isReadOnly(): boolean { return this._wrapped.isReadOnly(); } + public supportsLinks(): boolean { return this._wrapped.supportsProps(); } + public supportsProps(): boolean { return this._wrapped.supportsProps(); } + public supportsSynch(): boolean { return this._wrapped.supportsSynch(); } + public rename(oldPath: string, newPath: string, cb: BFSOneArgCallback): void { + return this._wrapped.rename(oldPath, newPath, cb); + } + public renameSync(oldPath: string, newPath: string): void { + return this._wrapped.renameSync(oldPath, newPath); + } + public stat(p: string, isLstat: boolean | null, cb: BFSCallback): void { + return this._wrapped.stat(p, isLstat, cb); + } + public statSync(p: string, isLstat: boolean | null): Stats { + return this._wrapped.statSync(p, isLstat); + } + public open(p: string, flag: FileFlag, mode: number, cb: BFSCallback): void { + return this._wrapped.open(p, flag, mode, cb); + } + public openSync(p: string, flag: FileFlag, mode: number): File { + return this._wrapped.openSync(p, flag, mode); + } + public unlink(p: string, cb: BFSOneArgCallback): void { + return this._wrapped.unlink(p, cb); + } + public unlinkSync(p: string): void { + return this._wrapped.unlinkSync(p); + } + public rmdir(p: string, cb: BFSOneArgCallback): void { + return this._wrapped.rmdir(p, cb); + } + public rmdirSync(p: string): void { + return this._wrapped.rmdirSync(p); + } + public mkdir(p: string, mode: number, cb: BFSOneArgCallback): void { + return this._wrapped.mkdir(p, mode, cb); + } + public mkdirSync(p: string, mode: number): void { + return this._wrapped.mkdirSync(p, mode); + } + public readdir(p: string, cb: BFSCallback): void { + return this._wrapped.readdir(p, cb); + } + public readdirSync(p: string): string[] { + return this._wrapped.readdirSync(p); + } + public exists(p: string, cb: (exists: boolean) => void): void { + return this._wrapped.exists(p, cb); + } + public existsSync(p: string): boolean { + return this._wrapped.existsSync(p); + } + public realpath(p: string, cache: {[path: string]: string}, cb: BFSCallback): void { + return this._wrapped.realpath(p, cache, cb); + } + public realpathSync(p: string, cache: {[path: string]: string}): string { + return this._wrapped.realpathSync(p, cache); + } + public truncate(p: string, len: number, cb: BFSOneArgCallback): void { + return this._wrapped.truncate(p, len, cb); + } + public truncateSync(p: string, len: number): void { + return this._wrapped.truncateSync(p, len); + } + public readFile(fname: string, encoding: string | null, flag: FileFlag, cb: BFSCallback): void { + return this._wrapped.readFile(fname, encoding, flag, cb); + } + public readFileSync(fname: string, encoding: string | null, flag: FileFlag): any { + this._beforeRead({fname, encoding, flag}); + const data = this._wrapped.readFileSync(fname, encoding, flag); + this._afterRead({fname, encoding, flag, data}); + return data; + } + public writeFile(fname: string, data: any, encoding: string | null, flag: FileFlag, mode: number, cb: BFSOneArgCallback): void { + return this._wrapped.writeFile(fname, data, encoding, flag, mode, cb); + } + public writeFileSync(fname: string, data: string | Buffer, encoding: string | null, flag: FileFlag, mode: number): void { + this._beforeWrite({fname, data, encoding, flag, mode}); + this._wrapped.writeFileSync(fname, data, encoding, flag, mode); + this._afterWrite({fname, data, encoding, flag, mode}); + return; + } + public appendFile(fname: string, data: string | Buffer, encoding: string | null, flag: FileFlag, mode: number, cb: BFSOneArgCallback): void { + return this._wrapped.appendFile(fname, data, encoding, flag, mode, cb); + } + public appendFileSync(fname: string, data: string | Buffer, encoding: string | null, flag: FileFlag, mode: number): void { + return this._wrapped.appendFileSync(fname, data, encoding, flag, mode); + } + public chmod(p: string, isLchmod: boolean, mode: number, cb: BFSOneArgCallback): void { + return this._wrapped.chmod(p, isLchmod, mode, cb); + } + public chmodSync(p: string, isLchmod: boolean, mode: number): void { + return this._wrapped.chmodSync(p, isLchmod, mode); + } + public chown(p: string, isLchown: boolean, uid: number, gid: number, cb: BFSOneArgCallback): void { + return this._wrapped.chown(p, isLchown, uid, gid, cb); + } + public chownSync(p: string, isLchown: boolean, uid: number, gid: number): void { + return this._wrapped.chownSync(p, isLchown, uid, gid); + } + public utimes(p: string, atime: Date, mtime: Date, cb: BFSOneArgCallback): void { + return this._wrapped.utimes(p, atime, mtime, cb); + } + public utimesSync(p: string, atime: Date, mtime: Date): void { + return this._wrapped.utimesSync(p, atime, mtime); + } + public link(srcpath: string, dstpath: string, cb: BFSOneArgCallback): void { + return this._wrapped.link(srcpath, dstpath, cb); + } + public linkSync(srcpath: string, dstpath: string): void { + return this._wrapped.linkSync(srcpath, dstpath); + } + public symlink(srcpath: string, dstpath: string, type: string, cb: BFSOneArgCallback): void { + return this._wrapped.symlink(srcpath, dstpath, type, cb); + } + public symlinkSync(srcpath: string, dstpath: string, type: string): void { + return this._wrapped.symlinkSync(srcpath, dstpath, type); + } + public readlink(p: string, cb: BFSCallback): void { + return this._wrapped.readlink(p, cb); + } + public readlinkSync(p: string): string { + return this._wrapped.readlinkSync(p); + } +} diff --git a/src/core/backends.ts b/src/core/backends.ts index 56d3d769..55d3db48 100644 --- a/src/core/backends.ts +++ b/src/core/backends.ts @@ -9,6 +9,7 @@ import HTML5FS from '../backend/HTML5FS'; import InMemory from '../backend/InMemory'; import IndexedDB from '../backend/IndexedDB'; import LocalStorage from '../backend/LocalStorage'; +import Middleware from '../backend/Middleware'; import MountableFileSystem from '../backend/MountableFileSystem'; import OverlayFS from '../backend/OverlayFS'; import WorkerFS from '../backend/WorkerFS'; @@ -17,7 +18,7 @@ import ZipFS from '../backend/ZipFS'; import IsoFS from '../backend/IsoFS'; // Monkey-patch `Create` functions to check options before file system initialization. -[AsyncMirror, Dropbox, Emscripten, FolderAdapter, HTML5FS, InMemory, IndexedDB, IsoFS, LocalStorage, MountableFileSystem, OverlayFS, WorkerFS, HTTPRequest, ZipFS].forEach((fsType: FileSystemConstructor) => { +[AsyncMirror, Dropbox, Emscripten, FolderAdapter, HTML5FS, InMemory, IndexedDB, IsoFS, LocalStorage, Middleware, MountableFileSystem, OverlayFS, WorkerFS, HTTPRequest, ZipFS].forEach((fsType: FileSystemConstructor) => { const create = fsType.Create; fsType.Create = function(opts?: any, cb?: BFSCallback): void { const oneArg = typeof(opts) === "function"; @@ -39,7 +40,7 @@ import IsoFS from '../backend/IsoFS'; /** * @hidden */ -const Backends = { AsyncMirror, Dropbox, Emscripten, FolderAdapter, HTML5FS, InMemory, IndexedDB, IsoFS, LocalStorage, MountableFileSystem, OverlayFS, WorkerFS, HTTPRequest, XmlHttpRequest: HTTPRequest, ZipFS }; +const Backends = { AsyncMirror, Dropbox, Emscripten, FolderAdapter, HTML5FS, InMemory, IndexedDB, IsoFS, LocalStorage, Middleware, MountableFileSystem, OverlayFS, WorkerFS, HTTPRequest, XmlHttpRequest: HTTPRequest, ZipFS }; // Make sure all backends cast to FileSystemConstructor (for type checking) const _: {[name: string]: FileSystemConstructor} = Backends; // tslint:disable-next-line:no-unused-expression diff --git a/test/harness/factories/middleware_factory.ts b/test/harness/factories/middleware_factory.ts new file mode 100644 index 00000000..9152e6e3 --- /dev/null +++ b/test/harness/factories/middleware_factory.ts @@ -0,0 +1,23 @@ +import {FileSystem} from '../../../src/core/file_system'; +import InMemory from '../../../src/backend/InMemory'; +import Middleware from '../../../src/backend/Middleware'; + +export default function HTTPDownloadFSFactory(cb: (name: string, objs: FileSystem[]) => void): void { + if (InMemory.isAvailable() && Middleware.isAvailable()) { + InMemory.Create({}, (e, mfs) => { + if (e) { + throw e; + } else { + Middleware.Create({ wrapped: mfs }, (e, fs) => { + if (e) { + throw e; + } else { + cb('Middleware', [fs]); + } + }); + } + }); + } else { + cb('Middleware', []); + } +} From cde06ba657dd624912aa3d24def63a5b15f726ea Mon Sep 17 00:00:00 2001 From: Will Hilton Date: Tue, 22 Aug 2017 23:08:28 -0400 Subject: [PATCH 3/4] Discard the "return a boolean for whether to proceed or not" idea --- src/backend/Middleware.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/backend/Middleware.ts b/src/backend/Middleware.ts index 06ed4b88..371ca71a 100644 --- a/src/backend/Middleware.ts +++ b/src/backend/Middleware.ts @@ -19,11 +19,10 @@ export interface MiddlewareOptions { writeTransformer?: Function; // (this, fname, enc, etc) => you implement } -function noop(args: any, cb?: BFSOneArgCallback) { +function noop(args: any, cb?: BFSOneArgCallback): void { if (cb) { - return cb(null); + cb(null); } - return true; } /** From c047e08d81dc48766d95e75bb2588dc8df6605e1 Mon Sep 17 00:00:00 2001 From: Will Hilton Date: Sat, 26 Aug 2017 23:27:51 -0400 Subject: [PATCH 4/4] Switch from using Create to getFileSystem --- test/harness/factories/middleware_factory.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/harness/factories/middleware_factory.ts b/test/harness/factories/middleware_factory.ts index 9152e6e3..4efe9a19 100644 --- a/test/harness/factories/middleware_factory.ts +++ b/test/harness/factories/middleware_factory.ts @@ -1,20 +1,20 @@ import {FileSystem} from '../../../src/core/file_system'; import InMemory from '../../../src/backend/InMemory'; import Middleware from '../../../src/backend/Middleware'; +import {getFileSystem} from '../../../src/core/browserfs'; -export default function HTTPDownloadFSFactory(cb: (name: string, objs: FileSystem[]) => void): void { +export default function MiddlewareFactory(cb: (name: string, objs: FileSystem[]) => void): void { if (InMemory.isAvailable() && Middleware.isAvailable()) { - InMemory.Create({}, (e, mfs) => { + getFileSystem({ + fs: "Middleware", + options: { + wrapped: { fs: 'InMemory' } + } + }, (e, fs?) => { if (e) { throw e; } else { - Middleware.Create({ wrapped: mfs }, (e, fs) => { - if (e) { - throw e; - } else { - cb('Middleware', [fs]); - } - }); + cb('Middleware', [fs]); } }); } else {