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