browserfs
Version:
A filesystem in your browser!
1,480 lines (1,378 loc) • 49 kB
text/typescript
import {File} from './file';
import {ApiError, ErrorCode} from './api_error';
import file_system = require('./file_system');
import {FileFlag} from './file_flag';
import path = require('path');
import Stats from './node_fs_stats';
// Typing info only.
import _fs = require('fs');
import global = require('./global');
declare var __numWaiting: number;
declare var setImmediate: (cb: Function) => void;
declare var RELEASE: boolean;
/**
* Wraps a callback with a setImmediate call.
* @param [Function] cb The callback to wrap.
* @param [Number] numArgs The number of arguments that the callback takes.
* @return [Function] The wrapped callback.
*/
function wrapCb<T extends Function>(cb: T, numArgs: number): T {
if (RELEASE) {
return cb;
} else {
if (typeof cb !== 'function') {
throw new ApiError(ErrorCode.EINVAL, 'Callback must be a function.');
}
// This is used for unit testing.
// We could use `arguments`, but Function.call/apply is expensive. And we only
// need to handle 1-3 arguments
if (typeof __numWaiting === 'undefined') {
global.__numWaiting = 0;
}
__numWaiting++;
switch (numArgs) {
case 1:
return <any> function(arg1: any) {
setImmediate(function() {
__numWaiting--;
return cb(arg1);
});
};
case 2:
return <any> function(arg1: any, arg2: any) {
setImmediate(function() {
__numWaiting--;
return cb(arg1, arg2);
});
};
case 3:
return <any> function(arg1: any, arg2: any, arg3: any) {
setImmediate(function() {
__numWaiting--;
return cb(arg1, arg2, arg3);
});
};
default:
throw new Error('Invalid invocation of wrapCb.');
}
}
}
function normalizeMode(mode: number|string, def: number): number {
switch(typeof mode) {
case 'number':
// (path, flag, mode, cb?)
return <number> mode;
case 'string':
// (path, flag, modeString, cb?)
var trueMode = parseInt(<string> mode, 8);
if (trueMode !== NaN) {
return trueMode;
}
// FALL THROUGH if mode is an invalid string!
default:
return def;
}
}
function normalizeTime(time: number | Date): Date {
if (time instanceof Date) {
return time;
} else if (typeof time === 'number') {
return new Date(time * 1000);
} else {
throw new ApiError(ErrorCode.EINVAL, `Invalid time.`);
}
}
function normalizePath(p: string): string {
// Node doesn't allow null characters in paths.
if (p.indexOf('\u0000') >= 0) {
throw new ApiError(ErrorCode.EINVAL, 'Path must be a string without null bytes.');
} else if (p === '') {
throw new ApiError(ErrorCode.EINVAL, 'Path must not be empty.');
}
return path.resolve(p);
}
function normalizeOptions(options: any, defEnc: string, defFlag: string, defMode: number): {encoding: string; flag: string; mode: number} {
switch (typeof options) {
case 'object':
return {
encoding: typeof options['encoding'] !== 'undefined' ? options['encoding'] : defEnc,
flag: typeof options['flag'] !== 'undefined' ? options['flag'] : defFlag,
mode: normalizeMode(options['mode'], defMode)
};
case 'string':
return {
encoding: options,
flag: defFlag,
mode: defMode
};
default:
return {
encoding: defEnc,
flag: defFlag,
mode: defMode
};
}
}
// The default callback is a NOP.
function nopCb() {};
/**
* The node frontend to all filesystems.
* This layer handles:
*
* * Sanity checking inputs.
* * Normalizing paths.
* * Resetting stack depth for asynchronous operations which may not go through
* the browser by wrapping all input callbacks using `setImmediate`.
* * Performing the requested operation through the filesystem or the file
* descriptor, as appropriate.
* * Handling optional arguments and setting default arguments.
* @see http://nodejs.org/api/fs.html
* @class
*/
export default class FS {
// Exported fs.Stats.
public static Stats = Stats;
private root: file_system.FileSystem = null;
private fdMap: {[fd: number]: File} = {};
private nextFd = 100;
private getFdForFile(file: File): number {
let fd = this.nextFd++;
this.fdMap[fd] = file;
return fd;
}
private fd2file(fd: number): File {
let rv = this.fdMap[fd];
if (rv) {
return rv;
} else {
throw new ApiError(ErrorCode.EBADF, 'Invalid file descriptor.');
}
}
private closeFd(fd: number): void {
delete this.fdMap[fd];
}
public initialize(rootFS: file_system.FileSystem): file_system.FileSystem {
if (!(<any> rootFS).constructor.isAvailable()) {
throw new ApiError(ErrorCode.EINVAL, 'Tried to instantiate BrowserFS with an unavailable file system.');
}
return this.root = rootFS;
}
/**
* converts Date or number to a fractional UNIX timestamp
* Grabbed from NodeJS sources (lib/fs.js)
*/
public _toUnixTimestamp(time: Date | number): number {
if (typeof time === 'number') {
return time;
} else if (time instanceof Date) {
return time.getTime() / 1000;
}
throw new Error("Cannot parse time: " + time);
}
/**
* **NONSTANDARD**: Grab the FileSystem instance that backs this API.
* @return [BrowserFS.FileSystem | null] Returns null if the file system has
* not been initialized.
*/
public getRootFS(): file_system.FileSystem {
if (this.root) {
return this.root;
} else {
return null;
}
}
// FILE OR DIRECTORY METHODS
/**
* Asynchronous rename. No arguments other than a possible exception are given
* to the completion callback.
* @param [String] oldPath
* @param [String] newPath
* @param [Function(BrowserFS.ApiError)] callback
*/
public rename(oldPath: string, newPath: string, cb: (err?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
this.root.rename(normalizePath(oldPath), normalizePath(newPath), newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous rename.
* @param [String] oldPath
* @param [String] newPath
*/
public renameSync(oldPath: string, newPath: string): void {
this.root.renameSync(normalizePath(oldPath), normalizePath(newPath));
}
/**
* Test whether or not the given path exists by checking with the file system.
* Then call the callback argument with either true or false.
* @example Sample invocation
* fs.exists('/etc/passwd', function (exists) {
* util.debug(exists ? "it's there" : "no passwd!");
* });
* @param [String] path
* @param [Function(Boolean)] callback
*/
public exists(path: string, cb: (exists: boolean) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
return this.root.exists(normalizePath(path), newCb);
} catch (e) {
// Doesn't return an error. If something bad happens, we assume it just
// doesn't exist.
return newCb(false);
}
}
/**
* Test whether or not the given path exists by checking with the file system.
* @param [String] path
* @return [boolean]
*/
public existsSync(path: string): boolean {
try {
return this.root.existsSync(normalizePath(path));
} catch (e) {
// Doesn't return an error. If something bad happens, we assume it just
// doesn't exist.
return false;
}
}
/**
* Asynchronous `stat`.
* @param [String] path
* @param [Function(BrowserFS.ApiError, BrowserFS.node.fs.Stats)] callback
*/
public stat(path: string, cb: (err: ApiError, stats?: Stats) => any = nopCb): void {
var newCb = wrapCb(cb, 2);
try {
return this.root.stat(normalizePath(path), false, newCb);
} catch (e) {
return newCb(e, null);
}
}
/**
* Synchronous `stat`.
* @param [String] path
* @return [BrowserFS.node.fs.Stats]
*/
public statSync(path: string): Stats {
return this.root.statSync(normalizePath(path), false);
}
/**
* Asynchronous `lstat`.
* `lstat()` is identical to `stat()`, except that if path is a symbolic link,
* then the link itself is stat-ed, not the file that it refers to.
* @param [String] path
* @param [Function(BrowserFS.ApiError, BrowserFS.node.fs.Stats)] callback
*/
public lstat(path: string, cb: (err: ApiError, stats?: Stats) => any = nopCb): void {
var newCb = wrapCb(cb, 2);
try {
return this.root.stat(normalizePath(path), true, newCb);
} catch (e) {
return newCb(e, null);
}
}
/**
* Synchronous `lstat`.
* `lstat()` is identical to `stat()`, except that if path is a symbolic link,
* then the link itself is stat-ed, not the file that it refers to.
* @param [String] path
* @return [BrowserFS.node.fs.Stats]
*/
public lstatSync(path: string): Stats {
return this.root.statSync(normalizePath(path), true);
}
// FILE-ONLY METHODS
/**
* Asynchronous `truncate`.
* @param [String] path
* @param [Number] len
* @param [Function(BrowserFS.ApiError)] callback
*/
public truncate(path: string, cb?: (err?: ApiError) => void): void;
public truncate(path: string, len: number, cb?: (err?: ApiError) => void): void;
public truncate(path: string, arg2: any = 0, cb: (err?: ApiError) => void = nopCb): void {
var len = 0;
if (typeof arg2 === 'function') {
cb = arg2;
} else if (typeof arg2 === 'number') {
len = arg2;
}
var newCb = wrapCb(cb, 1);
try {
if (len < 0) {
throw new ApiError(ErrorCode.EINVAL);
}
return this.root.truncate(normalizePath(path), len, newCb);
} catch (e) {
return newCb(e);
}
}
/**
* Synchronous `truncate`.
* @param [String] path
* @param [Number] len
*/
public truncateSync(path: string, len: number = 0): void {
if (len < 0) {
throw new ApiError(ErrorCode.EINVAL);
}
return this.root.truncateSync(normalizePath(path), len);
}
/**
* Asynchronous `unlink`.
* @param [String] path
* @param [Function(BrowserFS.ApiError)] callback
*/
public unlink(path: string, cb: (err?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
return this.root.unlink(normalizePath(path), newCb);
} catch (e) {
return newCb(e);
}
}
/**
* Synchronous `unlink`.
* @param [String] path
*/
public unlinkSync(path: string): void {
return this.root.unlinkSync(normalizePath(path));
}
/**
* Asynchronous file open.
* Exclusive mode ensures that path is newly created.
*
* `flags` can be:
*
* * `'r'` - Open file for reading. An exception occurs if the file does not exist.
* * `'r+'` - Open file for reading and writing. An exception occurs if the file does not exist.
* * `'rs'` - Open file for reading in synchronous mode. Instructs the filesystem to not cache writes.
* * `'rs+'` - Open file for reading and writing, and opens the file in synchronous mode.
* * `'w'` - Open file for writing. The file is created (if it does not exist) or truncated (if it exists).
* * `'wx'` - Like 'w' but opens the file in exclusive mode.
* * `'w+'` - Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists).
* * `'wx+'` - Like 'w+' but opens the file in exclusive mode.
* * `'a'` - Open file for appending. The file is created if it does not exist.
* * `'ax'` - Like 'a' but opens the file in exclusive mode.
* * `'a+'` - Open file for reading and appending. The file is created if it does not exist.
* * `'ax+'` - Like 'a+' but opens the file in exclusive mode.
*
* @see http://www.manpagez.com/man/2/open/
* @param [String] path
* @param [String] flags
* @param [Number?] mode defaults to `0644`
* @param [Function(BrowserFS.ApiError, BrowserFS.File)] callback
*/
public open(path: string, flag: string, cb?: (err: ApiError, fd?: number) => any): void;
public open(path: string, flag: string, mode: number|string, cb?: (err: ApiError, fd?: number) => any): void;
public open(path: string, flag: string, arg2?: any, cb: (err: ApiError, fd?: number) => any = nopCb): void {
var mode = normalizeMode(arg2, 0x1a4);
cb = typeof arg2 === 'function' ? arg2 : cb;
var newCb = wrapCb(cb, 2);
try {
this.root.open(normalizePath(path), FileFlag.getFileFlag(flag), mode, (e: ApiError, file?: File) => {
if (file) {
newCb(e, this.getFdForFile(file));
} else {
newCb(e);
}
});
} catch (e) {
newCb(e, null);
}
}
/**
* Synchronous file open.
* @see http://www.manpagez.com/man/2/open/
* @param [String] path
* @param [String] flags
* @param [Number?] mode defaults to `0644`
* @return [BrowserFS.File]
*/
public openSync(path: string, flag: string, mode: number|string = 0x1a4): number {
return this.getFdForFile(
this.root.openSync(normalizePath(path), FileFlag.getFileFlag(flag), normalizeMode(mode, 0x1a4)));
}
/**
* Asynchronously reads the entire contents of a file.
* @example Usage example
* fs.readFile('/etc/passwd', function (err, data) {
* if (err) throw err;
* console.log(data);
* });
* @param [String] filename
* @param [Object?] options
* @option options [String] encoding The string encoding for the file contents. Defaults to `null`.
* @option options [String] flag Defaults to `'r'`.
* @param [Function(BrowserFS.ApiError, String | BrowserFS.node.Buffer)] callback If no encoding is specified, then the raw buffer is returned.
*/
public readFile(filename: string, cb: (err: ApiError, data?: Buffer) => void ): void;
public readFile(filename: string, options: { flag?: string; }, callback: (err: ApiError, data: Buffer) => void): void;
public readFile(filename: string, options: { encoding: string; flag?: string; }, callback: (err: ApiError, data: string) => void): void;
public readFile(filename: string, encoding: string, cb?: (err: ApiError, data?: string) => void ): void;
public readFile(filename: string, arg2: any = {}, cb: (err: ApiError, data?: any) => void = nopCb ) {
var options = normalizeOptions(arg2, null, 'r', null);
cb = typeof arg2 === 'function' ? arg2 : cb;
var newCb = wrapCb(cb, 2);
try {
var flag = FileFlag.getFileFlag(options['flag']);
if (!flag.isReadable()) {
return newCb(new ApiError(ErrorCode.EINVAL, 'Flag passed to readFile must allow for reading.'));
}
return this.root.readFile(normalizePath(filename), options.encoding, flag, newCb);
} catch (e) {
return newCb(e, null);
}
}
/**
* Synchronously reads the entire contents of a file.
* @param [String] filename
* @param [Object?] options
* @option options [String] encoding The string encoding for the file contents. Defaults to `null`.
* @option options [String] flag Defaults to `'r'`.
* @return [String | BrowserFS.node.Buffer]
*/
public readFileSync(filename: string, options?: { flag?: string; }): Buffer;
public readFileSync(filename: string, options: { encoding: string; flag?: string; }): string;
public readFileSync(filename: string, encoding: string): string;
public readFileSync(filename: string, arg2: any = {}): any {
var options = normalizeOptions(arg2, null, 'r', null);
var flag = FileFlag.getFileFlag(options.flag);
if (!flag.isReadable()) {
throw new ApiError(ErrorCode.EINVAL, 'Flag passed to readFile must allow for reading.');
}
return this.root.readFileSync(normalizePath(filename), options.encoding, flag);
}
/**
* Asynchronously writes data to a file, replacing the file if it already
* exists.
*
* The encoding option is ignored if data is a buffer.
*
* @example Usage example
* fs.writeFile('message.txt', 'Hello Node', function (err) {
* if (err) throw err;
* console.log('It\'s saved!');
* });
* @param [String] filename
* @param [String | BrowserFS.node.Buffer] data
* @param [Object?] options
* @option options [String] encoding Defaults to `'utf8'`.
* @option options [Number] mode Defaults to `0644`.
* @option options [String] flag Defaults to `'w'`.
* @param [Function(BrowserFS.ApiError)] callback
*/
public writeFile(filename: string, data: any, cb?: (err?: ApiError) => void): void;
public writeFile(filename: string, data: any, encoding?: string, cb?: (err?: ApiError) => void): void;
public writeFile(filename: string, data: any, options?: { encoding?: string; mode?: string | number; flag?: string; }, cb?: (err?: ApiError) => void): void;
public writeFile(filename: string, data: any, arg3: any = {}, cb: (err?: ApiError) => void = nopCb): void {
var options = normalizeOptions(arg3, 'utf8', 'w', 0x1a4);
cb = typeof arg3 === 'function' ? arg3 : cb;
var newCb = wrapCb(cb, 1);
try {
var flag = FileFlag.getFileFlag(options.flag);
if (!flag.isWriteable()) {
return newCb(new ApiError(ErrorCode.EINVAL, 'Flag passed to writeFile must allow for writing.'));
}
return this.root.writeFile(normalizePath(filename), data, options.encoding, flag, options.mode, newCb);
} catch (e) {
return newCb(e);
}
}
/**
* Synchronously writes data to a file, replacing the file if it already
* exists.
*
* The encoding option is ignored if data is a buffer.
* @param [String] filename
* @param [String | BrowserFS.node.Buffer] data
* @param [Object?] options
* @option options [String] encoding Defaults to `'utf8'`.
* @option options [Number] mode Defaults to `0644`.
* @option options [String] flag Defaults to `'w'`.
*/
public writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number | string; flag?: string; }): void;
public writeFileSync(filename: string, data: any, encoding?: string): void;
public writeFileSync(filename: string, data: any, arg3?: any): void {
var options = normalizeOptions(arg3, 'utf8', 'w', 0x1a4);
var flag = FileFlag.getFileFlag(options.flag);
if (!flag.isWriteable()) {
throw new ApiError(ErrorCode.EINVAL, 'Flag passed to writeFile must allow for writing.');
}
return this.root.writeFileSync(normalizePath(filename), data, options.encoding, flag, options.mode);
}
/**
* Asynchronously append data to a file, creating the file if it not yet
* exists.
*
* @example Usage example
* fs.appendFile('message.txt', 'data to append', function (err) {
* if (err) throw err;
* console.log('The "data to append" was appended to file!');
* });
* @param [String] filename
* @param [String | BrowserFS.node.Buffer] data
* @param [Object?] options
* @option options [String] encoding Defaults to `'utf8'`.
* @option options [Number] mode Defaults to `0644`.
* @option options [String] flag Defaults to `'a'`.
* @param [Function(BrowserFS.ApiError)] callback
*/
public appendFile(filename: string, data: any, cb?: (err: ApiError) => void): void;
public appendFile(filename: string, data: any, options?: { encoding?: string; mode?: number|string; flag?: string; }, cb?: (err: ApiError) => void): void;
public appendFile(filename: string, data: any, encoding?: string, cb?: (err: ApiError) => void): void;
public appendFile(filename: string, data: any, arg3?: any, cb: (err: ApiError) => void = nopCb): void {
var options = normalizeOptions(arg3, 'utf8', 'a', 0x1a4);
cb = typeof arg3 === 'function' ? arg3 : cb;
var newCb = wrapCb(cb, 1);
try {
var flag = FileFlag.getFileFlag(options.flag);
if (!flag.isAppendable()) {
return newCb(new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.'));
}
this.root.appendFile(normalizePath(filename), data, options.encoding, flag, options.mode, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Asynchronously append data to a file, creating the file if it not yet
* exists.
*
* @example Usage example
* fs.appendFile('message.txt', 'data to append', function (err) {
* if (err) throw err;
* console.log('The "data to append" was appended to file!');
* });
* @param [String] filename
* @param [String | BrowserFS.node.Buffer] data
* @param [Object?] options
* @option options [String] encoding Defaults to `'utf8'`.
* @option options [Number] mode Defaults to `0644`.
* @option options [String] flag Defaults to `'a'`.
*/
public appendFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number | string; flag?: string; }): void;
public appendFileSync(filename: string, data: any, encoding?: string): void;
public appendFileSync(filename: string, data: any, arg3?: any): void {
var options = normalizeOptions(arg3, 'utf8', 'a', 0x1a4);
var flag = FileFlag.getFileFlag(options.flag);
if (!flag.isAppendable()) {
throw new ApiError(ErrorCode.EINVAL, 'Flag passed to appendFile must allow for appending.');
}
return this.root.appendFileSync(normalizePath(filename), data, options.encoding, flag, options.mode);
}
// FILE DESCRIPTOR METHODS
/**
* Asynchronous `fstat`.
* `fstat()` is identical to `stat()`, except that the file to be stat-ed is
* specified by the file descriptor `fd`.
* @param [BrowserFS.File] fd
* @param [Function(BrowserFS.ApiError, BrowserFS.node.fs.Stats)] callback
*/
public fstat(fd: number, cb: (err: ApiError, stats?: Stats) => any = nopCb): void {
var newCb = wrapCb(cb, 2);
try {
let file = this.fd2file(fd);
file.stat(newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `fstat`.
* `fstat()` is identical to `stat()`, except that the file to be stat-ed is
* specified by the file descriptor `fd`.
* @param [BrowserFS.File] fd
* @return [BrowserFS.node.fs.Stats]
*/
public fstatSync(fd: number): Stats {
return this.fd2file(fd).statSync();
}
/**
* Asynchronous close.
* @param [BrowserFS.File] fd
* @param [Function(BrowserFS.ApiError)] callback
*/
public close(fd: number, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
this.fd2file(fd).close((e: ApiError) => {
if (!e) {
this.closeFd(fd);
}
newCb(e);
});
} catch (e) {
newCb(e);
}
}
/**
* Synchronous close.
* @param [BrowserFS.File] fd
*/
public closeSync(fd: number): void {
this.fd2file(fd).closeSync();
this.closeFd(fd);
}
/**
* Asynchronous ftruncate.
* @param [BrowserFS.File] fd
* @param [Number] len
* @param [Function(BrowserFS.ApiError)] callback
*/
public ftruncate(fd: number, cb?: (err?: ApiError) => void): void;
public ftruncate(fd: number, len?: number, cb?: (err?: ApiError) => void): void;
public ftruncate(fd: number, arg2?: any, cb: (err?: ApiError) => void = nopCb): void {
var length = typeof arg2 === 'number' ? arg2 : 0;
cb = typeof arg2 === 'function' ? arg2 : cb;
var newCb = wrapCb(cb, 1);
try {
let file = this.fd2file(fd);
if (length < 0) {
throw new ApiError(ErrorCode.EINVAL);
}
file.truncate(length, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous ftruncate.
* @param [BrowserFS.File] fd
* @param [Number] len
*/
public ftruncateSync(fd: number, len: number = 0): void {
let file = this.fd2file(fd);
if (len < 0) {
throw new ApiError(ErrorCode.EINVAL);
}
file.truncateSync(len);
}
/**
* Asynchronous fsync.
* @param [BrowserFS.File] fd
* @param [Function(BrowserFS.ApiError)] callback
*/
public fsync(fd: number, cb: (err?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
this.fd2file(fd).sync(newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous fsync.
* @param [BrowserFS.File] fd
*/
public fsyncSync(fd: number): void {
this.fd2file(fd).syncSync();
}
/**
* Asynchronous fdatasync.
* @param [BrowserFS.File] fd
* @param [Function(BrowserFS.ApiError)] callback
*/
public fdatasync(fd: number, cb: (err?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
this.fd2file(fd).datasync(newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous fdatasync.
* @param [BrowserFS.File] fd
*/
public fdatasyncSync(fd: number): void {
this.fd2file(fd).datasyncSync();
}
/**
* Write buffer to the file specified by `fd`.
* Note that it is unsafe to use fs.write multiple times on the same file
* without waiting for the callback.
* @param [BrowserFS.File] fd
* @param [BrowserFS.node.Buffer] buffer Buffer containing the data to write to
* the file.
* @param [Number] offset Offset in the buffer to start reading data from.
* @param [Number] length The amount of bytes to write to the file.
* @param [Number] position Offset from the beginning of the file where this
* data should be written. If position is null, the data will be written at
* the current position.
* @param [Function(BrowserFS.ApiError, Number, BrowserFS.node.Buffer)]
* callback The number specifies the number of bytes written into the file.
*/
public write(fd: number, buffer: Buffer, offset: number, length: number, cb?: (err: ApiError, written: number, buffer: Buffer) => void): void;
public write(fd: number, buffer: Buffer, offset: number, length: number, position: number, cb?: (err: ApiError, written: number, buffer: Buffer) => void): void;
public write(fd: number, data: any, cb?: (err: ApiError, written: number, str: string) => any): void;
public write(fd: number, data: any, position: number, cb?: (err: ApiError, written: number, str: string) => any): void;
public write(fd: number, data: any, position: number, encoding: string, cb?: (err: ApiError, written: number, str: string) => void): void;
public write(fd: number, arg2: any, arg3?: any, arg4?: any, arg5?: any, cb: (err: ApiError, written?: number, buffer?: Buffer) => void = nopCb): void {
var buffer: Buffer, offset: number, length: number, position: number = null;
if (typeof arg2 === 'string') {
// Signature 1: (fd, string, [position?, [encoding?]], cb?)
var encoding = 'utf8';
switch (typeof arg3) {
case 'function':
// (fd, string, cb)
cb = arg3;
break;
case 'number':
// (fd, string, position, encoding?, cb?)
position = arg3;
encoding = typeof arg4 === 'string' ? arg4 : 'utf8';
cb = typeof arg5 === 'function' ? arg5 : cb;
break;
default:
// ...try to find the callback and get out of here!
cb = typeof arg4 === 'function' ? arg4 : typeof arg5 === 'function' ? arg5 : cb;
return cb(new ApiError(ErrorCode.EINVAL, 'Invalid arguments.'));
}
buffer = new Buffer(arg2, encoding);
offset = 0;
length = buffer.length;
} else {
// Signature 2: (fd, buffer, offset, length, position?, cb?)
buffer = arg2;
offset = arg3;
length = arg4;
position = typeof arg5 === 'number' ? arg5 : null;
cb = typeof arg5 === 'function' ? arg5 : cb;
}
var newCb = wrapCb(cb, 3);
try {
let file = this.fd2file(fd);
if (position == null) {
position = file.getPos();
}
file.write(buffer, offset, length, position, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Write buffer to the file specified by `fd`.
* Note that it is unsafe to use fs.write multiple times on the same file
* without waiting for it to return.
* @param [BrowserFS.File] fd
* @param [BrowserFS.node.Buffer] buffer Buffer containing the data to write to
* the file.
* @param [Number] offset Offset in the buffer to start reading data from.
* @param [Number] length The amount of bytes to write to the file.
* @param [Number] position Offset from the beginning of the file where this
* data should be written. If position is null, the data will be written at
* the current position.
* @return [Number]
*/
public writeSync(fd: number, buffer: Buffer, offset: number, length: number, position?: number): number;
public writeSync(fd: number, data: string, position?: number, encoding?: string): number;
public writeSync(fd: number, arg2: any, arg3?: any, arg4?: any, arg5?: any): number {
var buffer: Buffer, offset: number = 0, length: number, position: number;
if (typeof arg2 === 'string') {
// Signature 1: (fd, string, [position?, [encoding?]])
position = typeof arg3 === 'number' ? arg3 : null;
var encoding = typeof arg4 === 'string' ? arg4 : 'utf8';
offset = 0;
buffer = new Buffer(arg2, encoding);
length = buffer.length;
} else {
// Signature 2: (fd, buffer, offset, length, position?)
buffer = arg2;
offset = arg3;
length = arg4;
position = typeof arg5 === 'number' ? arg5 : null;
}
let file = this.fd2file(fd);
if (position == null) {
position = file.getPos();
}
return file.writeSync(buffer, offset, length, position);
}
/**
* Read data from the file specified by `fd`.
* @param [BrowserFS.File] fd
* @param [BrowserFS.node.Buffer] buffer The buffer that the data will be
* written to.
* @param [Number] offset The offset within the buffer where writing will
* start.
* @param [Number] length An integer specifying the number of bytes to read.
* @param [Number] position An integer specifying where to begin reading from
* in the file. If position is null, data will be read from the current file
* position.
* @param [Function(BrowserFS.ApiError, Number, BrowserFS.node.Buffer)]
* callback The number is the number of bytes read
*/
public read(fd: number, length: number, position: number, encoding: string, cb?: (err: ApiError, data?: string, bytesRead?: number) => void): void;
public read(fd: number, buffer: Buffer, offset: number, length: number, position: number, cb?: (err: ApiError, bytesRead?: number, buffer?: Buffer) => void): void;
public read(fd: number, arg2: any, arg3: any, arg4: any, arg5?: any, cb: (err: ApiError, arg2?: any, arg3?: any) => void = nopCb): void {
var position: number, offset: number, length: number, buffer: Buffer, newCb: (err: ApiError, bytesRead?: number, buffer?: Buffer) => void;
if (typeof arg2 === 'number') {
// legacy interface
// (fd, length, position, encoding, callback)
length = arg2;
position = arg3;
var encoding = arg4;
cb = typeof arg5 === 'function' ? arg5 : cb;
offset = 0;
buffer = new Buffer(length);
// XXX: Inefficient.
// Wrap the cb so we shelter upper layers of the API from these
// shenanigans.
newCb = wrapCb((function(err: any, bytesRead: number, buf: Buffer) {
if (err) {
return cb(err);
}
cb(err, buf.toString(encoding), bytesRead);
}), 3);
} else {
buffer = arg2;
offset = arg3;
length = arg4;
position = arg5;
newCb = wrapCb(cb, 3);
}
try {
let file = this.fd2file(fd);
if (position == null) {
position = file.getPos();
}
file.read(buffer, offset, length, position, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Read data from the file specified by `fd`.
* @param [BrowserFS.File] fd
* @param [BrowserFS.node.Buffer] buffer The buffer that the data will be
* written to.
* @param [Number] offset The offset within the buffer where writing will
* start.
* @param [Number] length An integer specifying the number of bytes to read.
* @param [Number] position An integer specifying where to begin reading from
* in the file. If position is null, data will be read from the current file
* position.
* @return [Number]
*/
public readSync(fd: number, length: number, position: number, encoding: string): string;
public readSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number;
public readSync(fd: number, arg2: any, arg3: any, arg4: any, arg5?: any): any {
var shenanigans = false;
var buffer: Buffer, offset: number, length: number, position: number;
if (typeof arg2 === 'number') {
length = arg2;
position = arg3;
var encoding = arg4;
offset = 0;
buffer = new Buffer(length);
shenanigans = true;
} else {
buffer = arg2;
offset = arg3;
length = arg4;
position = arg5;
}
let file = this.fd2file(fd);
if (position == null) {
position = file.getPos();
}
var rv = file.readSync(buffer, offset, length, position);
if (!shenanigans) {
return rv;
} else {
return [buffer.toString(encoding), rv];
}
}
/**
* Asynchronous `fchown`.
* @param [BrowserFS.File] fd
* @param [Number] uid
* @param [Number] gid
* @param [Function(BrowserFS.ApiError)] callback
*/
public fchown(fd: number, uid: number, gid: number, callback: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(callback, 1);
try {
this.fd2file(fd).chown(uid, gid, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `fchown`.
* @param [BrowserFS.File] fd
* @param [Number] uid
* @param [Number] gid
*/
public fchownSync(fd: number, uid: number, gid: number): void {
this.fd2file(fd).chownSync(uid, gid);
}
/**
* Asynchronous `fchmod`.
* @param [BrowserFS.File] fd
* @param [Number] mode
* @param [Function(BrowserFS.ApiError)] callback
*/
public fchmod(fd: number, mode: string | number, cb?: (e?: ApiError) => void): void {
var newCb = wrapCb(cb, 1);
try {
let numMode = typeof mode === 'string' ? parseInt(mode, 8) : mode;
this.fd2file(fd).chmod(numMode, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `fchmod`.
* @param [BrowserFS.File] fd
* @param [Number] mode
*/
public fchmodSync(fd: number, mode: number | string): void {
let numMode = typeof mode === 'string' ? parseInt(mode, 8) : mode;
this.fd2file(fd).chmodSync(numMode);
}
/**
* Change the file timestamps of a file referenced by the supplied file
* descriptor.
* @param [BrowserFS.File] fd
* @param [Date] atime
* @param [Date] mtime
* @param [Function(BrowserFS.ApiError)] callback
*/
public futimes(fd: number, atime: number, mtime: number, cb: (e?: ApiError) => void): void;
public futimes(fd: number, atime: Date, mtime: Date, cb: (e?: ApiError) => void): void;
public futimes(fd: number, atime: any, mtime: any, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
let file = this.fd2file(fd);
if (typeof atime === 'number') {
atime = new Date(atime * 1000);
}
if (typeof mtime === 'number') {
mtime = new Date(mtime * 1000);
}
file.utimes(atime, mtime, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Change the file timestamps of a file referenced by the supplied file
* descriptor.
* @param [BrowserFS.File] fd
* @param [Date] atime
* @param [Date] mtime
*/
public futimesSync(fd: number, atime: number | Date, mtime: number | Date): void {
this.fd2file(fd).utimesSync(normalizeTime(atime), normalizeTime(mtime));
}
// DIRECTORY-ONLY METHODS
/**
* Asynchronous `rmdir`.
* @param [String] path
* @param [Function(BrowserFS.ApiError)] callback
*/
public rmdir(path: string, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
path = normalizePath(path);
this.root.rmdir(path, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `rmdir`.
* @param [String] path
*/
public rmdirSync(path: string): void {
path = normalizePath(path);
return this.root.rmdirSync(path);
}
/**
* Asynchronous `mkdir`.
* @param [String] path
* @param [Number?] mode defaults to `0777`
* @param [Function(BrowserFS.ApiError)] callback
*/
public mkdir(path: string, mode?: any, cb: (e?: ApiError) => void = nopCb): void {
if (typeof mode === 'function') {
cb = mode;
mode = 0x1ff;
}
var newCb = wrapCb(cb, 1);
try {
path = normalizePath(path);
this.root.mkdir(path, mode, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `mkdir`.
* @param [String] path
* @param [Number?] mode defaults to `0777`
*/
public mkdirSync(path: string, mode?: number | string): void {
this.root.mkdirSync(normalizePath(path), normalizeMode(mode, 0x1ff));
}
/**
* 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 `'..'`.
* @param [String] path
* @param [Function(BrowserFS.ApiError, String[])] callback
*/
public readdir(path: string, cb: (err: ApiError, files?: string[]) => void = nopCb): void {
var newCb = <(err: ApiError, files?: string[]) => void> wrapCb(cb, 2);
try {
path = normalizePath(path);
this.root.readdir(path, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `readdir`. Reads the contents of a directory.
* @param [String] path
* @return [String[]]
*/
public readdirSync(path: string): string[] {
path = normalizePath(path);
return this.root.readdirSync(path);
}
// SYMLINK METHODS
/**
* Asynchronous `link`.
* @param [String] srcpath
* @param [String] dstpath
* @param [Function(BrowserFS.ApiError)] callback
*/
public link(srcpath: string, dstpath: string, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
srcpath = normalizePath(srcpath);
dstpath = normalizePath(dstpath);
this.root.link(srcpath, dstpath, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `link`.
* @param [String] srcpath
* @param [String] dstpath
*/
public linkSync(srcpath: string, dstpath: string): void {
srcpath = normalizePath(srcpath);
dstpath = normalizePath(dstpath);
return this.root.linkSync(srcpath, dstpath);
}
/**
* Asynchronous `symlink`.
* @param [String] srcpath
* @param [String] dstpath
* @param [String?] type can be either `'dir'` or `'file'` (default is `'file'`)
* @param [Function(BrowserFS.ApiError)] callback
*/
public symlink(srcpath: string, dstpath: string, cb?: (e?: ApiError) => void): void;
public symlink(srcpath: string, dstpath: string, type?: string, cb?: (e?: ApiError) => void): void;
public symlink(srcpath: string, dstpath: string, arg3?: any, cb: (e?: ApiError) => void = nopCb): void {
var type = typeof arg3 === 'string' ? arg3 : 'file';
cb = typeof arg3 === 'function' ? arg3 : cb;
var newCb = wrapCb(cb, 1);
try {
if (type !== 'file' && type !== 'dir') {
return newCb(new ApiError(ErrorCode.EINVAL, "Invalid type: " + type));
}
srcpath = normalizePath(srcpath);
dstpath = normalizePath(dstpath);
this.root.symlink(srcpath, dstpath, type, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `symlink`.
* @param [String] srcpath
* @param [String] dstpath
* @param [String?] type can be either `'dir'` or `'file'` (default is `'file'`)
*/
public symlinkSync(srcpath: string, dstpath: string, type?: string): void {
if (type == null) {
type = 'file';
} else if (type !== 'file' && type !== 'dir') {
throw new ApiError(ErrorCode.EINVAL, "Invalid type: " + type);
}
srcpath = normalizePath(srcpath);
dstpath = normalizePath(dstpath);
return this.root.symlinkSync(srcpath, dstpath, type);
}
/**
* Asynchronous readlink.
* @param [String] path
* @param [Function(BrowserFS.ApiError, String)] callback
*/
public readlink(path: string, cb: (err: ApiError, linkString?: string) => any = nopCb): void {
var newCb = wrapCb(cb, 2);
try {
path = normalizePath(path);
this.root.readlink(path, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous readlink.
* @param [String] path
* @return [String]
*/
public readlinkSync(path: string): string {
path = normalizePath(path);
return this.root.readlinkSync(path);
}
// PROPERTY OPERATIONS
/**
* Asynchronous `chown`.
* @param [String] path
* @param [Number] uid
* @param [Number] gid
* @param [Function(BrowserFS.ApiError)] callback
*/
public chown(path: string, uid: number, gid: number, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
path = normalizePath(path);
this.root.chown(path, false, uid, gid, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `chown`.
* @param [String] path
* @param [Number] uid
* @param [Number] gid
*/
public chownSync(path: string, uid: number, gid: number): void {
path = normalizePath(path);
this.root.chownSync(path, false, uid, gid);
}
/**
* Asynchronous `lchown`.
* @param [String] path
* @param [Number] uid
* @param [Number] gid
* @param [Function(BrowserFS.ApiError)] callback
*/
public lchown(path: string, uid: number, gid: number, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
path = normalizePath(path);
this.root.chown(path, true, uid, gid, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `lchown`.
* @param [String] path
* @param [Number] uid
* @param [Number] gid
*/
public lchownSync(path: string, uid: number, gid: number): void {
path = normalizePath(path);
this.root.chownSync(path, true, uid, gid);
}
/**
* Asynchronous `chmod`.
* @param [String] path
* @param [Number] mode
* @param [Function(BrowserFS.ApiError)] callback
*/
public chmod(path: string, mode: number | string, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
let numMode = normalizeMode(mode, -1);
if (numMode < 0) {
throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
}
this.root.chmod(normalizePath(path), false, numMode, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `chmod`.
* @param [String] path
* @param [Number] mode
*/
public chmodSync(path: string, mode: string|number): void {
let numMode = normalizeMode(mode, -1);
if (numMode < 0) {
throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
}
path = normalizePath(path);
this.root.chmodSync(path, false, numMode);
}
/**
* Asynchronous `lchmod`.
* @param [String] path
* @param [Number] mode
* @param [Function(BrowserFS.ApiError)] callback
*/
public lchmod(path: string, mode: number|string, cb: Function = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
let numMode = normalizeMode(mode, -1);
if (numMode < 0) {
throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
}
this.root.chmod(normalizePath(path), true, numMode, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `lchmod`.
* @param [String] path
* @param [Number] mode
*/
public lchmodSync(path: string, mode: number|string): void {
let numMode = normalizeMode(mode, -1);
if (numMode < 1) {
throw new ApiError(ErrorCode.EINVAL, `Invalid mode.`);
}
this.root.chmodSync(normalizePath(path), true, numMode);
}
/**
* Change file timestamps of the file referenced by the supplied path.
* @param [String] path
* @param [Date] atime
* @param [Date] mtime
* @param [Function(BrowserFS.ApiError)] callback
*/
public utimes(path: string, atime: number|Date, mtime: number|Date, cb: (e?: ApiError) => void = nopCb): void {
var newCb = wrapCb(cb, 1);
try {
this.root.utimes(normalizePath(path), normalizeTime(atime), normalizeTime(mtime), newCb);
} catch (e) {
newCb(e);
}
}
/**
* Change file timestamps of the file referenced by the supplied path.
* @param [String] path
* @param [Date] atime
* @param [Date] mtime
*/
public utimesSync(path: string, atime: number|Date, mtime: number|Date): void {
this.root.utimesSync(normalizePath(path), normalizeTime(atime), normalizeTime(mtime));
}
/**
* Asynchronous `realpath`. The callback gets two arguments
* `(err, resolvedPath)`. May use `process.cwd` to resolve relative paths.
*
* @example Usage example
* var cache = {'/etc':'/private/etc'};
* fs.realpath('/etc/passwd', cache, function (err, resolvedPath) {
* if (err) throw err;
* console.log(resolvedPath);
* });
*
* @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.
* @param [Function(BrowserFS.ApiError, String)] callback
*/
public realpath(path: string, cb?: (err: ApiError, resolvedPath?: string) =>any): void;
public realpath(path: string, cache: {[path: string]: string}, cb: (err: ApiError, resolvedPath?: string) =>any): void;
public realpath(path: string, arg2?: any, cb: (err: ApiError, resolvedPath?: string) => any = nopCb): void {
var cache = typeof arg2 === 'object' ? arg2 : {};
cb = typeof arg2 === 'function' ? arg2 : nopCb;
var newCb = <(err: ApiError, resolvedPath?: string) =>any> wrapCb(cb, 2);
try {
path = normalizePath(path);
this.root.realpath(path, cache, newCb);
} catch (e) {
newCb(e);
}
}
/**
* Synchronous `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.
* @return [String]
*/
public realpathSync(path: string, cache: {[path: string]: string} = {}): string {
path = normalizePath(path);
return this.root.realpathSync(path, cache);
}
public watchFile(filename: string, listener: (curr: Stats, prev: Stats) => void): void;
public watchFile(filename: string, options: { persistent?: boolean; interval?: number; }, listener: (curr: Stats, prev: Stats) => void): void;
public watchFile(filename: string, arg2: any, listener: (curr: Stats, prev: Stats) => void = nopCb): void {
throw new ApiError(ErrorCode.ENOTSUP);
}
public unwatchFile(filename: string, listener: (curr: Stats, prev: Stats) => void = nopCb): void {
throw new ApiError(ErrorCode.ENOTSUP);
}
public watch(filename: string, listener?: (event: string, filename: string) => any): _fs.FSWatcher;
public watch(filename: string, options: { persistent?: boolean; }, listener?: (event: string, filename: string) => any): _fs.FSWatcher;
public watch(filename: string, arg2: any, listener: (event: string, filename: string) => any = nopCb): _fs.FSWatcher {
throw new ApiError(ErrorCode.ENOTSUP);
}
public F_OK: number = 0;
public R_OK: number = 4;
public W_OK: number = 2;
public X_OK: number = 1;
public access(path: string, callback: (err: ApiError) => void): void;
public access(path: string, mode: number, callback: (err: ApiError) => void): void;
public access(path: string, arg2: any, cb: (e: ApiError) => void = nopCb): void {
throw new ApiError(ErrorCode.ENOTSUP);
}
public accessSync(path: string, mode?: number): void {
throw new ApiError(ErrorCode.ENOTSUP);
}
public createReadStream(path: string, options?: {
flags?: string;
encoding?: string;
fd?: number;
mode?: number;
autoClose?: boolean;
}): _fs.ReadStream {
throw new ApiError(ErrorCode.ENOTSUP);
}
public createWriteStream(path: string, options?: {
flags?: string;
encoding?: string;
fd?: number;
mode?: number;
}): _fs.WriteStream {
throw new ApiError(ErrorCode.ENOTSUP);
}
public _wrapCb: (cb: Function, args: number) => Function = wrapCb;
}
// Type checking.
var _: typeof _fs = new FS();
export interface FSModule extends FS {
/**
* Retrieve the FS object backing the fs module.
*/
getFSModule(): FS;
/**
* Set the FS object backing the fs module.
*/
changeFSModule(newFs: FS): void;
/**
* The FS constructor.
*/
FS: typeof FS;
}