diff options
author | Matt A. Tobin <email@mattatobin.com> | 2018-02-10 02:51:36 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2018-02-10 02:51:36 -0500 |
commit | 37d5300335d81cecbecc99812747a657588c63eb (patch) | |
tree | 765efa3b6a56bb715d9813a8697473e120436278 /toolkit/jetpack/sdk/io/fs.js | |
parent | b2bdac20c02b12f2057b9ef70b0a946113a00e00 (diff) | |
parent | 4fb11cd5966461bccc3ed1599b808237be6b0de9 (diff) | |
download | UXP-37d5300335d81cecbecc99812747a657588c63eb.tar UXP-37d5300335d81cecbecc99812747a657588c63eb.tar.gz UXP-37d5300335d81cecbecc99812747a657588c63eb.tar.lz UXP-37d5300335d81cecbecc99812747a657588c63eb.tar.xz UXP-37d5300335d81cecbecc99812747a657588c63eb.zip |
Merge branch 'ext-work'
Diffstat (limited to 'toolkit/jetpack/sdk/io/fs.js')
-rw-r--r-- | toolkit/jetpack/sdk/io/fs.js | 984 |
1 files changed, 984 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/io/fs.js b/toolkit/jetpack/sdk/io/fs.js new file mode 100644 index 000000000..860a884a5 --- /dev/null +++ b/toolkit/jetpack/sdk/io/fs.js @@ -0,0 +1,984 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +module.metadata = { + "stability": "experimental" +}; + +const { Cc, Ci, CC } = require("chrome"); + +const { setTimeout } = require("../timers"); +const { Stream, InputStream, OutputStream } = require("./stream"); +const { emit, on } = require("../event/core"); +const { Buffer } = require("./buffer"); +const { ns } = require("../core/namespace"); +const { Class } = require("../core/heritage"); + + +const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", + "initWithPath"); +const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1", + "nsIFileOutputStream", "init"); +const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", + "nsIFileInputStream", "init"); +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", "setInputStream"); +const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", "setOutputStream"); +const StreamPump = CC("@mozilla.org/network/input-stream-pump;1", + "nsIInputStreamPump", "init"); + +const { createOutputTransport, createInputTransport } = + Cc["@mozilla.org/network/stream-transport-service;1"]. + getService(Ci.nsIStreamTransportService); + +const { OPEN_UNBUFFERED } = Ci.nsITransport; + + +const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream; +const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile; +const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream; + +const FILE_PERMISSION = 0o666; +const PR_UINT32_MAX = 0xfffffff; +// Values taken from: +// http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615 +const PR_RDONLY = 0x01; +const PR_WRONLY = 0x02; +const PR_RDWR = 0x04; +const PR_CREATE_FILE = 0x08; +const PR_APPEND = 0x10; +const PR_TRUNCATE = 0x20; +const PR_SYNC = 0x40; +const PR_EXCL = 0x80; + +const FLAGS = { + "r": PR_RDONLY, + "r+": PR_RDWR, + "w": PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY, + "w+": PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR, + "a": PR_APPEND | PR_CREATE_FILE | PR_WRONLY, + "a+": PR_APPEND | PR_CREATE_FILE | PR_RDWR +}; + +function accessor() { + let map = new WeakMap(); + return function(fd, value) { + if (value === null) map.delete(fd); + if (value !== undefined) map.set(fd, value); + return map.get(fd); + } +} + +var nsIFile = accessor(); +var nsIFileInputStream = accessor(); +var nsIFileOutputStream = accessor(); +var nsIBinaryInputStream = accessor(); +var nsIBinaryOutputStream = accessor(); + +// Just a contstant object used to signal that all of the file +// needs to be read. +const ALL = new String("Read all of the file"); + +function isWritable(mode) { + return !!(mode & PR_WRONLY || mode & PR_RDWR); +} +function isReadable(mode) { + return !!(mode & PR_RDONLY || mode & PR_RDWR); +} + +function isString(value) { + return typeof(value) === "string"; +} +function isFunction(value) { + return typeof(value) === "function"; +} + +function toArray(enumerator) { + let value = []; + while(enumerator.hasMoreElements()) + value.push(enumerator.getNext()) + return value +} + +function getFileName(file) { + return file.QueryInterface(Ci.nsIFile).leafName; +} + + +function remove(path, recursive) { + let fd = new nsILocalFile(path) + if (fd.exists()) { + fd.remove(recursive || false); + } + else { + throw FSError("remove", "ENOENT", 34, path); + } +} + +/** + * Utility function to convert either an octal number or string + * into an octal number + * 0777 => 0o777 + * "0644" => 0o644 + */ +function Mode(mode, fallback) { + return isString(mode) ? parseInt(mode, 8) : mode || fallback; +} +function Flags(flag) { + return !isString(flag) ? flag : + FLAGS[flag] || Error("Unknown file open flag: " + flag); +} + + +function FSError(op, code, errno, path, file, line) { + let error = Error(code + ", " + op + " " + path, file, line); + error.code = code; + error.path = path; + error.errno = errno; + return error; +} + +const ReadStream = Class({ + extends: InputStream, + initialize: function initialize(path, options) { + this.position = -1; + this.length = -1; + this.flags = "r"; + this.mode = FILE_PERMISSION; + this.bufferSize = 64 * 1024; + + options = options || {}; + + if ("flags" in options && options.flags) + this.flags = options.flags; + if ("bufferSize" in options && options.bufferSize) + this.bufferSize = options.bufferSize; + if ("length" in options && options.length) + this.length = options.length; + if ("position" in options && options.position !== undefined) + this.position = options.position; + + let { flags, mode, position, length } = this; + let fd = isString(path) ? openSync(path, flags, mode) : path; + this.fd = fd; + + let input = nsIFileInputStream(fd); + // Setting a stream position, unless it"s `-1` which means current position. + if (position >= 0) + input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); + // We use `nsIStreamTransportService` service to transform blocking + // file input stream into a fully asynchronous stream that can be written + // without blocking the main thread. + let transport = createInputTransport(input, position, length, false); + // Open an input stream on a transport. We don"t pass flags to guarantee + // non-blocking stream semantics. Also we use defaults for segment size & + // count. + InputStream.prototype.initialize.call(this, { + asyncInputStream: transport.openInputStream(null, 0, 0) + }); + + // Close file descriptor on end and destroy the stream. + on(this, "end", _ => { + this.destroy(); + emit(this, "close"); + }); + + this.read(); + }, + destroy: function() { + closeSync(this.fd); + InputStream.prototype.destroy.call(this); + } +}); +exports.ReadStream = ReadStream; +exports.createReadStream = function createReadStream(path, options) { + return new ReadStream(path, options); +}; + +const WriteStream = Class({ + extends: OutputStream, + initialize: function initialize(path, options) { + this.drainable = true; + this.flags = "w"; + this.position = -1; + this.mode = FILE_PERMISSION; + + options = options || {}; + + if ("flags" in options && options.flags) + this.flags = options.flags; + if ("mode" in options && options.mode) + this.mode = options.mode; + if ("position" in options && options.position !== undefined) + this.position = options.position; + + let { position, flags, mode } = this; + // If pass was passed we create a file descriptor out of it. Otherwise + // we just use given file descriptor. + let fd = isString(path) ? openSync(path, flags, mode) : path; + this.fd = fd; + + let output = nsIFileOutputStream(fd); + // Setting a stream position, unless it"s `-1` which means current position. + if (position >= 0) + output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); + // We use `nsIStreamTransportService` service to transform blocking + // file output stream into a fully asynchronous stream that can be written + // without blocking the main thread. + let transport = createOutputTransport(output, position, -1, false); + // Open an output stream on a transport. We don"t pass flags to guarantee + // non-blocking stream semantics. Also we use defaults for segment size & + // count. + OutputStream.prototype.initialize.call(this, { + asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0), + output: output + }); + + // For write streams "finish" basically means close. + on(this, "finish", _ => { + this.destroy(); + emit(this, "close"); + }); + }, + destroy: function() { + OutputStream.prototype.destroy.call(this); + closeSync(this.fd); + } +}); +exports.WriteStream = WriteStream; +exports.createWriteStream = function createWriteStream(path, options) { + return new WriteStream(path, options); +}; + +const Stats = Class({ + initialize: function initialize(path) { + let file = new nsILocalFile(path); + if (!file.exists()) throw FSError("stat", "ENOENT", 34, path); + nsIFile(this, file); + }, + isDirectory: function() { + return nsIFile(this).isDirectory(); + }, + isFile: function() { + return nsIFile(this).isFile(); + }, + isSymbolicLink: function() { + return nsIFile(this).isSymlink(); + }, + get mode() { + return nsIFile(this).permissions; + }, + get size() { + return nsIFile(this).fileSize; + }, + get mtime() { + return nsIFile(this).lastModifiedTime; + }, + isBlockDevice: function() { + return nsIFile(this).isSpecial(); + }, + isCharacterDevice: function() { + return nsIFile(this).isSpecial(); + }, + isFIFO: function() { + return nsIFile(this).isSpecial(); + }, + isSocket: function() { + return nsIFile(this).isSpecial(); + }, + // non standard + get exists() { + return nsIFile(this).exists(); + }, + get hidden() { + return nsIFile(this).isHidden(); + }, + get writable() { + return nsIFile(this).isWritable(); + }, + get readable() { + return nsIFile(this).isReadable(); + } +}); +exports.Stats = Stats; + +const LStats = Class({ + extends: Stats, + get size() { + return this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink : + nsIFile(this).fileSize; + }, + get mtime() { + return this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink : + nsIFile(this).lastModifiedTime; + }, + // non standard + get permissions() { + return this.isSymbolicLink() ? nsIFile(this).permissionsOfLink : + nsIFile(this).permissions; + } +}); + +const FStat = Class({ + extends: Stats, + initialize: function initialize(fd) { + nsIFile(this, nsIFile(fd)); + } +}); + +function noop() {} +function Async(wrapped) { + return function (path, callback) { + let args = Array.slice(arguments); + callback = args.pop(); + // If node is not given a callback argument + // it just does not calls it. + if (typeof(callback) !== "function") { + args.push(callback); + callback = noop; + } + setTimeout(function() { + try { + var result = wrapped.apply(this, args); + if (result === undefined) callback(null); + else callback(null, result); + } catch (error) { + callback(error); + } + }, 0); + } +} + + +/** + * Synchronous rename(2) + */ +function renameSync(oldPath, newPath) { + let source = new nsILocalFile(oldPath); + let target = new nsILocalFile(newPath); + if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath); + return source.moveTo(target.parent, target.leafName); +}; +exports.renameSync = renameSync; + +/** + * Asynchronous rename(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var rename = Async(renameSync); +exports.rename = rename; + +/** + * Test whether or not the given path exists by checking with the file system. + */ +function existsSync(path) { + return new nsILocalFile(path).exists(); +} +exports.existsSync = existsSync; + +var exists = Async(existsSync); +exports.exists = exists; + +/** + * Synchronous ftruncate(2). + */ +function truncateSync(path, length) { + let fd = openSync(path, "w"); + ftruncateSync(fd, length); + closeSync(fd); +} +exports.truncateSync = truncateSync; + +/** + * Asynchronous ftruncate(2). No arguments other than a possible exception are + * given to the completion callback. + */ +function truncate(path, length, callback) { + open(path, "w", function(error, fd) { + if (error) return callback(error); + ftruncate(fd, length, function(error) { + if (error) { + closeSync(fd); + callback(error); + } + else { + close(fd, callback); + } + }); + }); +} +exports.truncate = truncate; + +function ftruncate(fd, length, callback) { + write(fd, new Buffer(length), 0, length, 0, function(error) { + callback(error); + }); +} +exports.ftruncate = ftruncate; + +function ftruncateSync(fd, length = 0) { + writeSync(fd, new Buffer(length), 0, length, 0); +} +exports.ftruncateSync = ftruncateSync; + +function chownSync(path, uid, gid) { + throw Error("Not implemented yet!!"); +} +exports.chownSync = chownSync; + +var chown = Async(chownSync); +exports.chown = chown; + +function lchownSync(path, uid, gid) { + throw Error("Not implemented yet!!"); +} +exports.lchownSync = chownSync; + +var lchown = Async(lchown); +exports.lchown = lchown; + +/** + * Synchronous chmod(2). + */ +function chmodSync (path, mode) { + let file; + try { + file = new nsILocalFile(path); + } catch(e) { + throw FSError("chmod", "ENOENT", 34, path); + } + + file.permissions = Mode(mode); +} +exports.chmodSync = chmodSync; +/** + * Asynchronous chmod(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var chmod = Async(chmodSync); +exports.chmod = chmod; + +/** + * Synchronous chmod(2). + */ +function fchmodSync(fd, mode) { + throw Error("Not implemented yet!!"); +}; +exports.fchmodSync = fchmodSync; +/** + * Asynchronous chmod(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var fchmod = Async(fchmodSync); +exports.fchmod = fchmod; + + +/** + * Synchronous stat(2). Returns an instance of `fs.Stats` + */ +function statSync(path) { + return new Stats(path); +}; +exports.statSync = statSync; + +/** + * Asynchronous stat(2). The callback gets two arguments (err, stats) where + * stats is a `fs.Stats` object. It looks like this: + */ +var stat = Async(statSync); +exports.stat = stat; + +/** + * Synchronous lstat(2). Returns an instance of `fs.Stats`. + */ +function lstatSync(path) { + return new LStats(path); +}; +exports.lstatSync = lstatSync; + +/** + * Asynchronous lstat(2). The callback gets two arguments (err, stats) where + * stats is a fs.Stats object. 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. + */ +var lstat = Async(lstatSync); +exports.lstat = lstat; + +/** + * Synchronous fstat(2). Returns an instance of `fs.Stats`. + */ +function fstatSync(fd) { + return new FStat(fd); +}; +exports.fstatSync = fstatSync; + +/** + * Asynchronous fstat(2). The callback gets two arguments (err, stats) where + * stats is a fs.Stats object. + */ +var fstat = Async(fstatSync); +exports.fstat = fstat; + +/** + * Synchronous link(2). + */ +function linkSync(source, target) { + throw Error("Not implemented yet!!"); +}; +exports.linkSync = linkSync; + +/** + * Asynchronous link(2). No arguments other than a possible exception are given + * to the completion callback. + */ +var link = Async(linkSync); +exports.link = link; + +/** + * Synchronous symlink(2). + */ +function symlinkSync(source, target) { + throw Error("Not implemented yet!!"); +}; +exports.symlinkSync = symlinkSync; + +/** + * Asynchronous symlink(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var symlink = Async(symlinkSync); +exports.symlink = symlink; + +/** + * Synchronous readlink(2). Returns the resolved path. + */ +function readlinkSync(path) { + return new nsILocalFile(path).target; +}; +exports.readlinkSync = readlinkSync; + +/** + * Asynchronous readlink(2). The callback gets two arguments + * `(error, resolvedPath)`. + */ +var readlink = Async(readlinkSync); +exports.readlink = readlink; + +/** + * Synchronous realpath(2). Returns the resolved path. + */ +function realpathSync(path) { + return new nsILocalFile(path).path; +}; +exports.realpathSync = realpathSync; + +/** + * Asynchronous realpath(2). The callback gets two arguments + * `(err, resolvedPath)`. + */ +var realpath = Async(realpathSync); +exports.realpath = realpath; + +/** + * Synchronous unlink(2). + */ +var unlinkSync = remove; +exports.unlinkSync = unlinkSync; + +/** + * Asynchronous unlink(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var unlink = Async(remove); +exports.unlink = unlink; + +/** + * Synchronous rmdir(2). + */ +var rmdirSync = remove; +exports.rmdirSync = rmdirSync; + +/** + * Asynchronous rmdir(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var rmdir = Async(rmdirSync); +exports.rmdir = rmdir; + +/** + * Synchronous mkdir(2). + */ +function mkdirSync(path, mode) { + try { + return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode)); + } catch (error) { + // Adjust exception thorw to match ones thrown by node. + if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") { + let { fileName, lineNumber } = error; + error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber); + } + throw error; + } +}; +exports.mkdirSync = mkdirSync; + +/** + * Asynchronous mkdir(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var mkdir = Async(mkdirSync); +exports.mkdir = mkdir; + +/** + * Synchronous readdir(3). Returns an array of filenames excluding `"."` and + * `".."`. + */ +function readdirSync(path) { + try { + return toArray(new nsILocalFile(path).directoryEntries).map(getFileName); + } + catch (error) { + // Adjust exception thorw to match ones thrown by node. + if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" || + error.name === "NS_ERROR_FILE_NOT_FOUND") + { + let { fileName, lineNumber } = error; + error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber); + } + throw error; + } +}; +exports.readdirSync = readdirSync; + +/** + * Asynchronous readdir(3). Reads the contents of a directory. The callback + * gets two arguments `(error, files)` where `files` is an array of the names + * of the files in the directory excluding `"."` and `".."`. + */ +var readdir = Async(readdirSync); +exports.readdir = readdir; + +/** + * Synchronous close(2). + */ + function closeSync(fd) { + let input = nsIFileInputStream(fd); + let output = nsIFileOutputStream(fd); + + // Closing input stream and removing reference. + if (input) input.close(); + // Closing output stream and removing reference. + if (output) output.close(); + + nsIFile(fd, null); + nsIFileInputStream(fd, null); + nsIFileOutputStream(fd, null); + nsIBinaryInputStream(fd, null); + nsIBinaryOutputStream(fd, null); +}; +exports.closeSync = closeSync; +/** + * Asynchronous close(2). No arguments other than a possible exception are + * given to the completion callback. + */ +var close = Async(closeSync); +exports.close = close; + +/** + * Synchronous open(2). + */ +function openSync(aPath, aFlag, aMode) { + let [ fd, flags, mode, file ] = + [ { path: aPath }, Flags(aFlag), Mode(aMode), nsILocalFile(aPath) ]; + + nsIFile(fd, file); + + // If trying to open file for just read that does not exists + // need to throw exception as node does. + if (!file.exists() && !isWritable(flags)) + throw FSError("open", "ENOENT", 34, aPath); + + // If we want to open file in read mode we initialize input stream. + if (isReadable(flags)) { + let input = FileInputStream(file, flags, mode, DEFER_OPEN); + nsIFileInputStream(fd, input); + } + + // If we want to open file in write mode we initialize output stream for it. + if (isWritable(flags)) { + let output = FileOutputStream(file, flags, mode, DEFER_OPEN); + nsIFileOutputStream(fd, output); + } + + return fd; +} +exports.openSync = openSync; +/** + * Asynchronous file open. See open(2). Flags can be + * `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`. + * The callback gets two arguments `(error, fd). + */ +var open = Async(openSync); +exports.open = open; + +/** + * Synchronous version of buffer-based fs.write(). Returns the number of bytes + * written. + */ +function writeSync(fd, buffer, offset, length, position) { + if (length + offset > buffer.length) { + throw Error("Length is extends beyond buffer"); + } + else if (length + offset !== buffer.length) { + buffer = buffer.slice(offset, offset + length); + } + + let output = BinaryOutputStream(nsIFileOutputStream(fd)); + nsIBinaryOutputStream(fd, output); + // We write content as a byte array as this will avoid any transcoding + // if content was a buffer. + output.writeByteArray(buffer.valueOf(), buffer.length); + output.flush(); +}; +exports.writeSync = writeSync; + +/** + * Write buffer to the file specified by fd. + * + * `offset` and `length` determine the part of the buffer to be written. + * + * `position` refers to the 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. See pwrite(2). + * + * The callback will be given three arguments `(error, written, buffer)` where + * written specifies how many bytes were written into buffer. + * + * Note that it is unsafe to use `fs.write` multiple times on the same file + * without waiting for the callback. + */ +function write(fd, buffer, offset, length, position, callback) { + if (!Buffer.isBuffer(buffer)) { + // (fd, data, position, encoding, callback) + let encoding = null; + [ position, encoding, callback ] = Array.slice(arguments, 1); + buffer = new Buffer(String(buffer), encoding); + offset = 0; + } else if (length + offset > buffer.length) { + throw Error("Length is extends beyond buffer"); + } else if (length + offset !== buffer.length) { + buffer = buffer.slice(offset, offset + length); + } + + let writeStream = new WriteStream(fd, { position: position, + length: length }); + writeStream.on("error", callback); + writeStream.write(buffer, function onEnd() { + writeStream.destroy(); + if (callback) + callback(null, buffer.length, buffer); + }); +}; +exports.write = write; + +/** + * Synchronous version of string-based fs.read. Returns the number of + * bytes read. + */ +function readSync(fd, buffer, offset, length, position) { + let input = nsIFileInputStream(fd); + // Setting a stream position, unless it"s `-1` which means current position. + if (position >= 0) + input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); + // We use `nsIStreamTransportService` service to transform blocking + // file input stream into a fully asynchronous stream that can be written + // without blocking the main thread. + let binaryInputStream = BinaryInputStream(input); + let count = length === ALL ? binaryInputStream.available() : length; + if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer); + else { + let chunk = new Buffer(count); + binaryInputStream.readArrayBuffer(count, chunk.buffer); + chunk.copy(buffer, offset); + } + + return buffer.slice(offset, offset + count); +}; +exports.readSync = readSync; + +/** + * Read data from the file specified by `fd`. + * + * `buffer` is the buffer that the data will be written to. + * `offset` is offset within the buffer where writing will start. + * + * `length` is an integer specifying the number of bytes to read. + * + * `position` is an integer specifying where to begin reading from in the file. + * If `position` is `null`, data will be read from the current file position. + * + * The callback is given the three arguments, `(error, bytesRead, buffer)`. + */ +function read(fd, buffer, offset, length, position, callback) { + let bytesRead = 0; + let readStream = new ReadStream(fd, { position: position, length: length }); + readStream.on("data", function onData(data) { + data.copy(buffer, offset + bytesRead); + bytesRead += data.length; + }); + readStream.on("end", function onEnd() { + callback(null, bytesRead, buffer); + readStream.destroy(); + }); +}; +exports.read = read; + +/** + * Asynchronously reads the entire contents of a file. + * The callback is passed two arguments `(error, data)`, where data is the + * contents of the file. + */ +function readFile(path, encoding, callback) { + if (isFunction(encoding)) { + callback = encoding + encoding = null + } + + let buffer = null; + try { + let readStream = new ReadStream(path); + readStream.on("data", function(data) { + if (!buffer) buffer = data; + else buffer = Buffer.concat([buffer, data], 2); + }); + readStream.on("error", function onError(error) { + callback(error); + }); + readStream.on("end", function onEnd() { + // Note: Need to destroy before invoking a callback + // so that file descriptor is released. + readStream.destroy(); + callback(null, buffer); + }); + } + catch (error) { + setTimeout(callback, 0, error); + } +}; +exports.readFile = readFile; + +/** + * Synchronous version of `fs.readFile`. Returns the contents of the path. + * If encoding is specified then this function returns a string. + * Otherwise it returns a buffer. + */ +function readFileSync(path, encoding) { + let fd = openSync(path, "r"); + let size = fstatSync(fd).size; + let buffer = new Buffer(size); + try { + readSync(fd, buffer, 0, ALL, 0); + } + finally { + closeSync(fd); + } + return buffer; +}; +exports.readFileSync = readFileSync; + +/** + * Asynchronously writes data to a file, replacing the file if it already + * exists. data can be a string or a buffer. + */ +function writeFile(path, content, encoding, callback) { + if (!isString(path)) + throw new TypeError('path must be a string'); + + try { + if (isFunction(encoding)) { + callback = encoding + encoding = null + } + if (isString(content)) + content = new Buffer(content, encoding); + + let writeStream = new WriteStream(path); + let error = null; + + writeStream.end(content, function() { + writeStream.destroy(); + callback(error); + }); + + writeStream.on("error", function onError(reason) { + error = reason; + writeStream.destroy(); + }); + } catch (error) { + callback(error); + } +}; +exports.writeFile = writeFile; + +/** + * The synchronous version of `fs.writeFile`. + */ +function writeFileSync(filename, data, encoding) { + // TODO: Implement this in bug 1148209 https://bugzilla.mozilla.org/show_bug.cgi?id=1148209 + throw Error("Not implemented"); +}; +exports.writeFileSync = writeFileSync; + + +function utimesSync(path, atime, mtime) { + throw Error("Not implemented"); +} +exports.utimesSync = utimesSync; + +var utimes = Async(utimesSync); +exports.utimes = utimes; + +function futimesSync(fd, atime, mtime, callback) { + throw Error("Not implemented"); +} +exports.futimesSync = futimesSync; + +var futimes = Async(futimesSync); +exports.futimes = futimes; + +function fsyncSync(fd, atime, mtime, callback) { + throw Error("Not implemented"); +} +exports.fsyncSync = fsyncSync; + +var fsync = Async(fsyncSync); +exports.fsync = fsync; + + +/** + * Watch for changes on filename. The callback listener will be called each + * time the file is accessed. + * + * The second argument is optional. The options if provided should be an object + * containing two members a boolean, persistent, and interval, a polling value + * in milliseconds. The default is { persistent: true, interval: 0 }. + */ +function watchFile(path, options, listener) { + throw Error("Not implemented"); +}; +exports.watchFile = watchFile; + + +function unwatchFile(path, listener) { + throw Error("Not implemented"); +} +exports.unwatchFile = unwatchFile; + +function watch(path, options, listener) { + throw Error("Not implemented"); +} +exports.watch = watch; |