UNPKG

browserfs

Version:

A filesystem in your browser!

1,018 lines (998 loc) 33.9 kB
import {ApiError, ErrorCode} from './api_error'; import Stats from './node_fs_stats'; import file = require('./file'); import {FileFlag, ActionType} from './file_flag'; import path = require('path'); /** * Interface for a filesystem. **All** BrowserFS FileSystems should implement * this interface. * * Below, we denote each API method as **Core**, **Supplemental**, or * **Optional**. * * ### Core Methods * * **Core** API methods *need* to be implemented for basic read/write * functionality. * * Note that read-only FileSystems can choose to not implement core methods * that mutate files or metadata. The default implementation will pass a * NOT_SUPPORTED error to the callback. * * ### Supplemental Methods * * **Supplemental** API methods do not need to be implemented by a filesystem. * The default implementation implements all of the supplemental API methods in * terms of the **core** API methods. * * Note that a file system may choose to implement supplemental methods for * efficiency reasons. * * The code for some supplemental methods was adapted directly from NodeJS's * fs.js source code. * * ### Optional Methods * * **Optional** API methods provide functionality that may not be available in * all filesystems. For example, all symlink/hardlink-related API methods fall * under this category. * * The default implementation will pass a NOT_SUPPORTED error to the callback. * * ### Argument Assumptions * * You can assume the following about arguments passed to each API method: * * * **Every path is an absolute path.** Meaning, `.`, `..`, and other items * are resolved into an absolute form. * * **All arguments are present.** Any optional arguments at the Node API level * have been passed in with their default values. * * **The callback will reset the stack depth.** When your filesystem calls the * callback with the requested information, it will use `setImmediate` to * reset the JavaScript stack depth before calling the user-supplied callback. * @class FileSystem */ export interface FileSystem { /** * **Optional**: Returns the name of the file system. * @method FileSystem#getName * @return {string} */ getName(): string; /** * **Optional**: Passes the following information to the callback: * * * Total number of bytes available on this file system. * * number of free bytes available on this file system. * * @method FileSystem#diskSpace * @todo This info is not available through the Node API. Perhaps we could do a * polyfill of diskspace.js, or add a new Node API function. * @param {string} path The path to the location that is being queried. Only * useful for filesystems that support mount points. * @param {FileSystem~diskSpaceCallback} cb */ diskSpace(p: string, cb: (total: number, free: number) => any): void; /** * **Core**: Is this filesystem read-only? * @method FileSystem#isReadOnly * @return {boolean} True if this FileSystem is inherently read-only. */ isReadOnly(): boolean; /** * **Core**: Does the filesystem support optional symlink/hardlink-related * commands? * @method FileSystem#supportsLinks * @return {boolean} True if the FileSystem supports the optional * symlink/hardlink-related commands. */ supportsLinks(): boolean; /** * **Core**: Does the filesystem support optional property-related commands? * @method FileSystem#supportsProps * @return {boolean} True if the FileSystem supports the optional * property-related commands (permissions, utimes, etc). */ supportsProps(): boolean; /** * **Core**: Does the filesystem support the optional synchronous interface? * @method FileSystem#supportsSynch * @return {boolean} True if the FileSystem supports synchronous operations. */ supportsSynch(): boolean; // **CORE API METHODS** // File or directory operations /** * **Core**: Asynchronous rename. No arguments other than a possible exception * are given to the completion callback. * @method FileSystem#rename * @param {string} oldPath * @param {string} newPath * @param {FileSystem~nodeCallback} cb */ rename(oldPath: string, newPath: string, cb: (err?: ApiError) => void): void; /** * **Core**: Synchronous rename. * @method FileSystem#renameSync * @param {string} oldPath * @param {string} newPath */ renameSync(oldPath: string, newPath: string): void; /** * **Core**: Asynchronous `stat` or `lstat`. * @method FileSystem#stat * @param {string} path * @param {boolean} isLstat True if this is `lstat`, false if this is regular * `stat`. * @param {FileSystem~nodeStatsCallback} cb */ stat(p: string, isLstat: boolean, cb: (err: ApiError, stat?: Stats) => void): void; /** * **Core**: Synchronous `stat` or `lstat`. * @method FileSystem#statSync * @param {string} path * @param {boolean} isLstat True if this is `lstat`, false if this is regular * `stat`. * @return {BrowserFS.node.fs.Stats} */ statSync(p: string, isLstat: boolean): Stats; // File operations /** * **Core**: Asynchronous file open. * @see http://www.manpagez.com/man/2/open/ * @method FileSystem#open * @param {string} path * @param {BrowserFS.FileMode} flags Handles the complexity of the various file * modes. See its API for more details. * @param {number} mode Mode to use to open the file. Can be ignored if the * filesystem doesn't support permissions. * @param {FileSystem~fileCallback} cb */ open(p: string, flag:FileFlag, mode: number, cb: (err: ApiError, fd?: file.File) => any): void; /** * **Core**: Synchronous file open. * @see http://www.manpagez.com/man/2/open/ * @method FileSystem#openSync * @param {string} path * @param {BrowserFS.FileMode} flags Handles the complexity of the various file * modes. See its API for more details. * @param {number} mode Mode to use to open the file. Can be ignored if the * filesystem doesn't support permissions. * @return {BrowserFS.File} */ openSync(p: string, flag: FileFlag, mode: number): file.File; /** * **Core**: Asynchronous `unlink`. * @method FileSystem#unlink * @param [string] path * @param [FileSystem~nodeCallback] cb */ unlink(p: string, cb: Function): void; /** * **Core**: Synchronous `unlink`. * @method FileSystem#unlinkSync * @param {string} path */ unlinkSync(p: string): void; // Directory operations /** * **Core**: Asynchronous `rmdir`. * @method FileSystem#rmdir * @param {string} path * @param {FileSystem~nodeCallback} cb */ rmdir(p: string, cb: Function): void; /** * **Core**: Synchronous `rmdir`. * @method FileSystem#rmdirSync * @param {string} path */ rmdirSync(p: string): void; /** * **Core**: Asynchronous `mkdir`. * @method FileSystem#mkdir * @param {string} path * @param {number?} mode Mode to make the directory using. Can be ignored if * the filesystem doesn't support permissions. * @param {FileSystem~nodeCallback} cb */ mkdir(p: string, mode: number, cb: Function): void; /** * **Core**: Synchronous `mkdir`. * @method FileSystem#mkdirSync * @param {string} path * @param {number} mode Mode to make the directory using. Can be ignored if * the filesystem doesn't support permissions. */ mkdirSync(p: string, mode: number): void; /** * **Core**: Asynchronous `readdir`. Reads the contents of a directory. * * The callback gets two arguments `(err, files)` where `files` is an array of * the names of the files in the directory excluding `'.'` and `'..'`. * @method FileSystem#readdir * @param {string} path * @param {FileSystem~readdirCallback} cb */ readdir(p: string, cb: (err: ApiError, files?: string[]) => void): void; /** * **Core**: Synchronous `readdir`. Reads the contents of a directory. * @method FileSystem#readdirSync * @param {string} path * @return {string[]} */ readdirSync(p: string): string[]; // **SUPPLEMENTAL INTERFACE METHODS** // File or directory operations /** * **Supplemental**: Test whether or not the given path exists by checking with * the file system. Then call the callback argument with either true or false. * @method FileSystem#exists * @param {string} path * @param {FileSystem~existsCallback} cb */ exists(p: string, cb: (exists: boolean) => void): void; /** * **Supplemental**: Test whether or not the given path exists by checking with * the file system. * @method FileSystem#existsSync * @param {string} path * @return {boolean} */ existsSync(p: string): boolean; /** * **Supplemental**: Asynchronous `realpath`. The callback gets two arguments * `(err, resolvedPath)`. * * Note that the Node API will resolve `path` to an absolute path. * @method FileSystem#realpath * @param {string} path * @param {Object} cache An object literal of mapped paths that can be used to * force a specific path resolution or avoid additional `fs.stat` calls for * known real paths. If not supplied by the user, it'll be an empty object. * @param {FileSystem~pathCallback} cb */ realpath(p: string, cache: {[path: string]: string}, cb: (err: ApiError, resolvedPath?: string) => any): void; /** * **Supplemental**: Synchronous `realpath`. * * Note that the Node API will resolve `path` to an absolute path. * @method FileSystem#realpathSync * @param {string} path * @param {Object} cache An object literal of mapped paths that can be used to * force a specific path resolution or avoid additional `fs.stat` calls for * known real paths. If not supplied by the user, it'll be an empty object. * @return {string} */ realpathSync(p: string, cache: {[path: string]: string}): string; // File operations /** * * **Supplemental**: Asynchronous `truncate`. * @method FileSystem#truncate * @param {string} path * @param {number} len * @param {FileSystem~nodeCallback} cb */ truncate(p: string, len: number, cb: Function): void; /** * **Supplemental**: Synchronous `truncate`. * @method FileSystem#truncateSync * @param {string} path * @param {number} len */ truncateSync(p: string, len: number): void; /** * **Supplemental**: Asynchronously reads the entire contents of a file. * @method FileSystem#readFile * @param {string} filename * @param {string} encoding If non-null, the file's contents should be decoded * into a string using that encoding. Otherwise, if encoding is null, fetch * the file's contents as a Buffer. * @param {BrowserFS.FileMode} flag * @param {FileSystem~readCallback} cb If no encoding is specified, then the * raw buffer is returned. */ readFile(fname: string, encoding: string, flag: FileFlag, cb: (err: ApiError, data?: any) => void): void; /** * **Supplemental**: Synchronously reads the entire contents of a file. * @method FileSystem#readFileSync * @param {string} filename * @param {string} encoding If non-null, the file's contents should be decoded * into a string using that encoding. Otherwise, if encoding is null, fetch * the file's contents as a Buffer. * @param {BrowserFS.FileMode} flag * @return {(string|BrowserFS.Buffer)} */ readFileSync(fname: string, encoding: string, flag: FileFlag): any; /** * **Supplemental**: Asynchronously writes data to a file, replacing the file * if it already exists. * * The encoding option is ignored if data is a buffer. * @method FileSystem#writeFile * @param {string} filename * @param {(string | BrowserFS.node.Buffer)} data * @param {string} encoding * @param {BrowserFS.FileMode} flag * @param {number} mode * @param {FileSystem~nodeCallback} cb */ writeFile(fname: string, data: any, encoding: string, flag: FileFlag, mode: number, cb: (err: ApiError) => void): void; /** * **Supplemental**: Synchronously writes data to a file, replacing the file * if it already exists. * * The encoding option is ignored if data is a buffer. * @method FileSystem#writeFileSync * @param {string} filename * @param {(string | BrowserFS.node.Buffer)} data * @param {string} encoding * @param {BrowserFS.FileMode} flag * @param {number} mode */ writeFileSync(fname: string, data: any, encoding: string, flag: FileFlag, mode: number): void; /** * **Supplemental**: Asynchronously append data to a file, creating the file if * it not yet exists. * @method FileSystem#appendFile * @param {string} filename * @param {(string | BrowserFS.node.Buffer)} data * @param {string} encoding * @param {BrowserFS.FileMode} flag * @param {number} mode * @param {FileSystem~nodeCallback} cb */ appendFile(fname: string, data: any, encoding: string, flag: FileFlag, mode: number, cb: (err: ApiError) => void): void; /** * **Supplemental**: Synchronously append data to a file, creating the file if * it not yet exists. * @method FileSystem#appendFileSync * @param {string} filename * @param {(string | BrowserFS.node.Buffer)} data * @param {string} encoding * @param {BrowserFS.FileMode} flag * @param {number} mode */ appendFileSync(fname: string, data: any, encoding: string, flag: FileFlag, mode: number): void; // **OPTIONAL INTERFACE METHODS** // Property operations // This isn't always possible on some filesystem types (e.g. Dropbox). /** * **Optional**: Asynchronous `chmod` or `lchmod`. * @method FileSystem#chmod * @param {string} path * @param {boolean} isLchmod `True` if `lchmod`, false if `chmod`. Has no * bearing on result if links aren't supported. * @param {number} mode * @param {FileSystem~nodeCallback} cb */ chmod(p: string, isLchmod: boolean, mode: number, cb: Function): void; /** * **Optional**: Synchronous `chmod` or `lchmod`. * @method FileSystem#chmodSync * @param {string} path * @param {boolean} isLchmod `True` if `lchmod`, false if `chmod`. Has no * bearing on result if links aren't supported. * @param {number} mode */ chmodSync(p: string, isLchmod: boolean, mode: number): void; /** * **Optional**: Asynchronous `chown` or `lchown`. * @method FileSystem#chown * @param {string} path * @param {boolean} isLchown `True` if `lchown`, false if `chown`. Has no * bearing on result if links aren't supported. * @param {number} uid * @param {number} gid * @param {FileSystem~nodeCallback} cb */ chown(p: string, isLchown: boolean, uid: number, gid: number, cb: Function): void; /** * **Optional**: Synchronous `chown` or `lchown`. * @method FileSystem#chownSync * @param {string} path * @param {boolean} isLchown `True` if `lchown`, false if `chown`. Has no * bearing on result if links aren't supported. * @param {number} uid * @param {number} gid */ chownSync(p: string, isLchown: boolean, uid: number, gid: number): void; /** * **Optional**: Change file timestamps of the file referenced by the supplied * path. * @method FileSystem#utimes * @param {string} path * @param {Date} atime * @param {Date} mtime * @param {FileSystem~nodeCallback} cb */ utimes(p: string, atime: Date, mtime: Date, cb: Function): void; /** * **Optional**: Change file timestamps of the file referenced by the supplied * path. * @method FileSystem#utimesSync * @param {string} path * @param {Date} atime * @param {Date} mtime */ utimesSync(p: string, atime: Date, mtime: Date): void; // Symlink operations // Symlinks aren't always supported. /** * **Optional**: Asynchronous `link`. * @method FileSystem#link * @param {string} srcpath * @param {string} dstpath * @param {FileSystem~nodeCallback} cb */ link(srcpath: string, dstpath: string, cb: Function): void; /** * **Optional**: Synchronous `link`. * @method FileSystem#linkSync * @param {string} srcpath * @param {string} dstpath */ linkSync(srcpath: string, dstpath: string): void; /** * **Optional**: Asynchronous `symlink`. * @method FileSystem#symlink * @param {string} srcpath * @param {string} dstpath * @param {string} type can be either `'dir'` or `'file'` * @param {FileSystem~nodeCallback} cb */ symlink(srcpath: string, dstpath: string, type: string, cb: Function): void; /** * **Optional**: Synchronous `symlink`. * @method FileSystem#symlinkSync * @param {string} srcpath * @param {string} dstpath * @param {string} type can be either `'dir'` or `'file'` */ symlinkSync(srcpath: string, dstpath: string, type: string): void; /** * **Optional**: Asynchronous readlink. * @method FileSystem#readlink * @param {string} path * @param {FileSystem~pathCallback} callback */ readlink(p: string, cb: Function): void; /** * **Optional**: Synchronous readlink. * @method FileSystem#readlinkSync * @param {string} path */ readlinkSync(p: string): string; } /** * Contains typings for static functions on the file system constructor. */ export interface FileSystemConstructor { /** * **Core**: Returns 'true' if this filesystem is available in the current * environment. For example, a `localStorage`-backed filesystem will return * 'false' if the browser does not support that API. * * Defaults to 'false', as the FileSystem base class isn't usable alone. * @method FileSystem.isAvailable * @return {boolean} */ isAvailable(): boolean; } /** * Basic filesystem class. Most filesystems should extend this class, as it * provides default implementations for a handful of methods. */ export class BaseFileSystem { public supportsLinks(): boolean { return false; } public diskSpace(p: string, cb: (total: number, free: number) => any): void { cb(0, 0); } /** * Opens the file at path p with the given flag. The file must exist. * @param p The path to open. * @param flag The flag to use when opening the file. */ public openFile(p: string, flag: FileFlag, cb: (e: ApiError, file?: file.File) => void): void { throw new ApiError(ErrorCode.ENOTSUP); } /** * Create the file at path p with the given mode. Then, open it with the given * flag. */ public createFile(p: string, flag: FileFlag, mode: number, cb: (e: ApiError, file?: file.File) => void): void { throw new ApiError(ErrorCode.ENOTSUP); } public open(p: string, flag:FileFlag, mode: number, cb: (err: ApiError, fd?: file.BaseFile) => any): void { var must_be_file = (e: ApiError, stats?: Stats): void => { if (e) { // File does not exist. switch (flag.pathNotExistsAction()) { case ActionType.CREATE_FILE: // Ensure parent exists. return this.stat(path.dirname(p), false, (e: ApiError, parentStats?: Stats) => { if (e) { cb(e); } else if (!parentStats.isDirectory()) { cb(ApiError.ENOTDIR(path.dirname(p))); } else { this.createFile(p, flag, mode, cb); } }); case ActionType.THROW_EXCEPTION: return cb(ApiError.ENOENT(p)); default: return cb(new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.')); } } else { // File exists. if (stats.isDirectory()) { return cb(ApiError.EISDIR(p)); } switch (flag.pathExistsAction()) { case ActionType.THROW_EXCEPTION: return cb(ApiError.EEXIST(p)); case ActionType.TRUNCATE_FILE: // NOTE: In a previous implementation, we deleted the file and // re-created it. However, this created a race condition if another // asynchronous request was trying to read the file, as the file // would not exist for a small period of time. return this.openFile(p, flag, (e: ApiError, fd?: file.File): void => { if (e) { cb(e); } else { fd.truncate(0, () => { fd.sync(() => { cb(null, fd); }); }); } }); case ActionType.NOP: return this.openFile(p, flag, cb); default: return cb(new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.')); } } }; this.stat(p, false, must_be_file); } public rename(oldPath: string, newPath: string, cb: (err?: ApiError) => void): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public renameSync(oldPath: string, newPath: string): void { throw new ApiError(ErrorCode.ENOTSUP); } public stat(p: string, isLstat: boolean, cb: (err: ApiError, stat?: Stats) => void): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public statSync(p: string, isLstat: boolean): Stats { throw new ApiError(ErrorCode.ENOTSUP); } /** * Opens the file at path p with the given flag. The file must exist. * @param p The path to open. * @param flag The flag to use when opening the file. * @return A File object corresponding to the opened file. */ public openFileSync(p: string, flag: FileFlag, mode: number): file.File { throw new ApiError(ErrorCode.ENOTSUP); } /** * Create the file at path p with the given mode. Then, open it with the given * flag. */ public createFileSync(p: string, flag: FileFlag, mode: number): file.File { throw new ApiError(ErrorCode.ENOTSUP); } public openSync(p: string, flag: FileFlag, mode: number): file.File { // Check if the path exists, and is a file. var stats: Stats; try { stats = this.statSync(p, false); } catch (e) { // File does not exist. switch (flag.pathNotExistsAction()) { case ActionType.CREATE_FILE: // Ensure parent exists. var parentStats = this.statSync(path.dirname(p), false); if (!parentStats.isDirectory()) { throw ApiError.ENOTDIR(path.dirname(p)); } return this.createFileSync(p, flag, mode); case ActionType.THROW_EXCEPTION: throw ApiError.ENOENT(p); default: throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.'); } } // File exists. if (stats.isDirectory()) { throw ApiError.EISDIR(p); } switch (flag.pathExistsAction()) { case ActionType.THROW_EXCEPTION: throw ApiError.EEXIST(p); case ActionType.TRUNCATE_FILE: // Delete file. this.unlinkSync(p); // Create file. Use the same mode as the old file. // Node itself modifies the ctime when this occurs, so this action // will preserve that behavior if the underlying file system // supports those properties. return this.createFileSync(p, flag, stats.mode); case ActionType.NOP: return this.openFileSync(p, flag, mode); default: throw new ApiError(ErrorCode.EINVAL, 'Invalid FileFlag object.'); } } public unlink(p: string, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public unlinkSync(p: string): void { throw new ApiError(ErrorCode.ENOTSUP); } public rmdir(p: string, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public rmdirSync(p: string): void { throw new ApiError(ErrorCode.ENOTSUP); } public mkdir(p: string, mode: number, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public mkdirSync(p: string, mode: number): void { throw new ApiError(ErrorCode.ENOTSUP); } public readdir(p: string, cb: (err: ApiError, files?: string[]) => void): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public readdirSync(p: string): string[] { throw new ApiError(ErrorCode.ENOTSUP); } public exists(p: string, cb: (exists: boolean) => void): void { this.stat(p, null, function(err) { cb(err == null); }); } public existsSync(p: string): boolean { try { this.statSync(p, true); return true; } catch (e) { return false; } } public realpath(p: string, cache: {[path: string]: string}, cb: (err: ApiError, resolvedPath?: string) => any): void { if (this.supportsLinks()) { // The path could contain symlinks. Split up the path, // resolve any symlinks, return the resolved string. var splitPath = p.split(path.sep); // TODO: Simpler to just pass through file, find sep and such. for (var i = 0; i < splitPath.length; i++) { var addPaths = splitPath.slice(0, i + 1); splitPath[i] = path.join.apply(null, addPaths); } } else { // No symlinks. We just need to verify that it exists. this.exists(p, function(doesExist) { if (doesExist) { cb(null, p); } else { cb(ApiError.ENOENT(p)); } }); } } public realpathSync(p: string, cache: {[path: string]: string}): string { if (this.supportsLinks()) { // The path could contain symlinks. Split up the path, // resolve any symlinks, return the resolved string. var splitPath = p.split(path.sep); // TODO: Simpler to just pass through file, find sep and such. for (var i = 0; i < splitPath.length; i++) { var addPaths = splitPath.slice(0, i + 1); splitPath[i] = path.join.apply(null, addPaths); } } else { // No symlinks. We just need to verify that it exists. if (this.existsSync(p)) { return p; } else { throw ApiError.ENOENT(p); } } } public truncate(p: string, len: number, cb: Function): void { this.open(p, FileFlag.getFileFlag('r+'), 0x1a4, (function(er: ApiError, fd?: file.File) { if (er) { return cb(er); } fd.truncate(len, (function(er: any) { fd.close((function(er2: any) { cb(er || er2); })); })); })); } public truncateSync(p: string, len: number): void { var fd = this.openSync(p, FileFlag.getFileFlag('r+'), 0x1a4); // Need to safely close FD, regardless of whether or not truncate succeeds. try { fd.truncateSync(len); } catch (e) { throw e; } finally { fd.closeSync(); } } public readFile(fname: string, encoding: string, flag: FileFlag, cb: (err: ApiError, data?: any) => void): void { // Wrap cb in file closing code. var oldCb = cb; // Get file. this.open(fname, flag, 0x1a4, function(err: ApiError, fd?: file.File) { if (err) { return cb(err); } cb = function(err: ApiError, arg?: file.File) { fd.close(function(err2: any) { if (err == null) { err = err2; } return oldCb(err, arg); }); }; fd.stat(function(err: ApiError, stat?: Stats) { if (err != null) { return cb(err); } // Allocate buffer. var buf = new Buffer(stat.size); fd.read(buf, 0, stat.size, 0, function(err) { if (err != null) { return cb(err); } else if (encoding === null) { return cb(err, buf); } try { cb(null, buf.toString(encoding)); } catch (e) { cb(e); } }); }); }); } public readFileSync(fname: string, encoding: string, flag: FileFlag): any { // Get file. var fd = this.openSync(fname, flag, 0x1a4); try { var stat = fd.statSync(); // Allocate buffer. var buf = new Buffer(stat.size); fd.readSync(buf, 0, stat.size, 0); fd.closeSync(); if (encoding === null) { return buf; } return buf.toString(encoding); } finally { fd.closeSync(); } } public writeFile(fname: string, data: any, encoding: string, flag: FileFlag, mode: number, cb: (err: ApiError) => void): void { // Wrap cb in file closing code. var oldCb = cb; // Get file. this.open(fname, flag, 0x1a4, function(err: ApiError, fd?:file.File) { if (err != null) { return cb(err); } cb = function(err: ApiError) { fd.close(function(err2: any) { oldCb(err != null ? err : err2); }); }; try { if (typeof data === 'string') { data = new Buffer(data, encoding); } } catch (e) { return cb(e); } // Write into file. fd.write(data, 0, data.length, 0, cb); }); } public writeFileSync(fname: string, data: any, encoding: string, flag: FileFlag, mode: number): void { // Get file. var fd = this.openSync(fname, flag, mode); try { if (typeof data === 'string') { data = new Buffer(data, encoding); } // Write into file. fd.writeSync(data, 0, data.length, 0); } finally { fd.closeSync(); } } public appendFile(fname: string, data: any, encoding: string, flag: FileFlag, mode: number, cb: (err: ApiError) => void): void { // Wrap cb in file closing code. var oldCb = cb; this.open(fname, flag, mode, function(err: ApiError, fd?: file.File) { if (err != null) { return cb(err); } cb = function(err: ApiError) { fd.close(function(err2: any) { oldCb(err != null ? err : err2); }); }; if (typeof data === 'string') { data = new Buffer(data, encoding); } fd.write(data, 0, data.length, null, cb); }); } public appendFileSync(fname: string, data: any, encoding: string, flag: FileFlag, mode: number): void { var fd = this.openSync(fname, flag, mode); try { if (typeof data === 'string') { data = new Buffer(data, encoding); } fd.writeSync(data, 0, data.length, null); } finally { fd.closeSync(); } } public chmod(p: string, isLchmod: boolean, mode: number, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public chmodSync(p: string, isLchmod: boolean, mode: number) { throw new ApiError(ErrorCode.ENOTSUP); } public chown(p: string, isLchown: boolean, uid: number, gid: number, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public chownSync(p: string, isLchown: boolean, uid: number, gid: number): void { throw new ApiError(ErrorCode.ENOTSUP); } public utimes(p: string, atime: Date, mtime: Date, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public utimesSync(p: string, atime: Date, mtime: Date): void { throw new ApiError(ErrorCode.ENOTSUP); } public link(srcpath: string, dstpath: string, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public linkSync(srcpath: string, dstpath: string): void { throw new ApiError(ErrorCode.ENOTSUP); } public symlink(srcpath: string, dstpath: string, type: string, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public symlinkSync(srcpath: string, dstpath: string, type: string): void { throw new ApiError(ErrorCode.ENOTSUP); } public readlink(p: string, cb: Function): void { cb(new ApiError(ErrorCode.ENOTSUP)); } public readlinkSync(p: string): string { throw new ApiError(ErrorCode.ENOTSUP); } } /** * Implements the asynchronous API in terms of the synchronous API. * @class SynchronousFileSystem */ export class SynchronousFileSystem extends BaseFileSystem { public supportsSynch(): boolean { return true; } public rename(oldPath: string, newPath: string, cb: Function): void { try { this.renameSync(oldPath, newPath); cb(); } catch (e) { cb(e); } } public stat(p: string, isLstat: boolean, cb: Function): void { try { cb(null, this.statSync(p, isLstat)); } catch (e) { cb(e); } } public open(p: string, flags: FileFlag, mode: number, cb: Function): void { try { cb(null, this.openSync(p, flags, mode)); } catch (e) { cb(e); } } public unlink(p: string, cb: Function): void { try { this.unlinkSync(p); cb(); } catch (e) { cb(e); } } public rmdir(p: string, cb: Function): void { try { this.rmdirSync(p); cb(); } catch (e) { cb(e); } } public mkdir(p: string, mode: number, cb: Function): void { try { this.mkdirSync(p, mode); cb(); } catch (e) { cb(e); } } public readdir(p: string, cb: Function): void { try { cb(null, this.readdirSync(p)); } catch (e) { cb(e); } } public chmod(p: string, isLchmod: boolean, mode: number, cb: Function): void { try { this.chmodSync(p, isLchmod, mode); cb(); } catch (e) { cb(e); } } public chown(p: string, isLchown: boolean, uid: number, gid: number, cb: Function): void { try { this.chownSync(p, isLchown, uid, gid); cb(); } catch (e) { cb(e); } } public utimes(p: string, atime: Date, mtime: Date, cb: Function): void { try { this.utimesSync(p, atime, mtime); cb(); } catch (e) { cb(e); } } public link(srcpath: string, dstpath: string, cb: Function): void { try { this.linkSync(srcpath, dstpath); cb(); } catch (e) { cb(e); } } public symlink(srcpath: string, dstpath: string, type: string, cb: Function): void { try { this.symlinkSync(srcpath, dstpath, type); cb(); } catch (e) { cb(e); } } public readlink(p: string, cb: Function): void { try { cb(null, this.readlinkSync(p)); } catch (e) { cb(e); } } }