A synchronous output function. Will block the process and
output +string+ immediately to +stdout+.
++log(string)+::
+Output with timestamp.
+inspect(object, showHidden, depth)+ ::
+stats.isSocket()+:: ...
+ === +fs.FileReadStream+
+
+ [cols="1,2,10",options="header"]
+ |=========================================================
+ |Event | Parameters | Notes
+
+ |+"open"+ | +fd+ | The file descriptor was opened.
+ |+"data"+ | +chunk+ | A chunk of data was read.
+ |+"error"+ | +err+ | An error occured. This stops the stream.
+ |+"end"+ | | The end of the file was reached.
+ |+"close"+ | | The file descriptor was closed.
+ |=========================================================
+
+ +fs.createReadStream(path, [options]);+ ::
+ Returns a new FileReadStream object.
+ +
+ +options+ is an object with the following defaults:
+ +
+ ----------------------------------------
+ { "flags": "r"
+ , "encoding": "binary"
+ , "mode": 0666
+ , "bufferSize": 4 * 1024
+ }
+ ----------------------------------------
+
+ +readStream.readable+ ::
+ A boolean that is +true+ by default, but turns +false+ after an +"error"+
+ occured, the stream came to an "end", or +forceClose()+ was called.
+
+ +readStream.pause()+ ::
+ Stops the stream from reading further data. No +"data"+ event will be fired
+ until the stream is resumed.
+
+ +readStream.resume()+ ::
+ Resumes the stream. Together with +pause()+ this useful to throttle reading.
+
+ +readStream.forceClose()+ ::
+ Allows to close the stream before the +"end"+ is reached. No more events other
+ than +"close"+ will be fired after this method has been called.
+
+ === +fs.FileWriteStream+
+
+ [cols="1,2,10",options="header"]
+ |=========================================================
+ |Event | Parameters | Notes
+
+ |+"open"+ | +fd+ | The file descriptor was opened.
+ |+"drain"+ | | No more data needs to be written.
+ |+"error"+ | +err+ | An error occured. This stops the stream.
+ |+"close"+ | | The file descriptor was closed.
+ |=========================================================
+
+ +fs.createWriteStream(path, [options]);+ ::
+ Returns a new FileWriteStream object.
+ +
+ +options+ is an object with the following defaults:
+ +
+ ----------------------------------------
+ { "flags": "r"
+ , "encoding": "binary"
+ , "mode": 0666
+ }
+ ----------------------------------------
+
+ +writeStream.writeable+ ::
+ A boolean that is +true+ by default, but turns +false+ after an +"error"+
+ occured or +close()+ / +forceClose()+ was called.
+
+ +writeStream.write(data)+ ::
+ Returns +true+ if the data was flushed to the kernel, and +false+ if it was
+ queued up for being written later. A +"drain"+ will fire after all queued data
+ has been written.
+
+ +writeStream.close()+ ::
+ Closes the stream right after all queued +write()+ calls have finished.
+
+ +writeStream.forceClose()+ ::
+ Allows to close the stream regardless of its current state.
+
== HTTP
To use the HTTP server and client one must +require("http")+.
Sends data on the connection. The second parameter specifies the encoding
in the case of a string--it defaults to ASCII because encoding to UTF8 is
rather slow.
++
+Returns +true+ if the entire data was flushed successfully to the kernel
+buffer. Returns +false+ if all or part of the data was queued in user memory.
++'drain'+ will be emitted when the buffer is again free.
+connection.close()+::
+ var
+ sys = require('sys'),
+ events = require('events');
+
exports.Stats = process.Stats;
process.Stats.prototype._checkModeProperty = function (property) {
// Realpath
var path = require('path');
-var dirname = path.dirname,
- basename = path.basename,
- normalize = path.normalize;
-
-function readlinkDeepSync(path, stats) {
- var seen_links = {}, resolved_link, stats, file_id;
- while (true) {
- stats = stats || exports.lstatSync(path);
- file_id = stats.dev.toString(32)+":"+stats.ino.toString(32);
- if (file_id in seen_links) {
- throw new Error("cyclic symbolic link at "+path);
- } else {
- seen_links[file_id] = 1;
- if (stats.isSymbolicLink()) {
- var newpath = exports.readlinkSync(path);
- if (newpath.charAt(0) === '/') {
- path = newpath;
+var normalize = path.normalize
+ normalizeArray = path.normalizeArray;
+
+exports.realpathSync = function (path) {
+ var seen_links = {}, knownHards = {}, buf, i = 0, part, x, stats;
+ if (path.charAt(0) !== '/') {
+ var cwd = process.cwd().split('/');
+ path = cwd.concat(path.split('/'));
+ path = normalizeArray(path);
+ i = cwd.length;
+ buf = [].concat(cwd);
+ } else {
+ path = normalizeArray(path.split('/'));
+ buf = [''];
+ }
+ for (; i<path.length; i++) {
+ part = path.slice(0, i+1).join('/');
+ if (part.length !== 0) {
+ if (part in knownHards) {
+ buf.push(path[i]);
+ } else {
+ stats = exports.lstatSync(part);
+ if (stats.isSymbolicLink()) {
+ x = stats.dev.toString(32)+":"+stats.ino.toString(32);
+ if (x in seen_links)
+ throw new Error("cyclic link at "+part);
+ seen_links[x] = true;
+ part = exports.readlinkSync(part);
+ if (part.charAt(0) === '/') {
+ // absolute
+ path = normalizeArray(part.split('/'));
+ buf = [''];
+ i = 0;
+ } else {
+ // relative
+ Array.prototype.splice.apply(path, [i, 1].concat(part.split('/')));
+ part = normalizeArray(path);
+ var y = 0, L = Math.max(path.length, part.length), delta;
+ for (; y<L && path[y] === part[y]; y++);
+ if (y !== L) {
+ path = part;
+ delta = i-y;
+ i = y-1;
+ if (delta > 0) buf.splice(y, delta);
+ } else {
+ i--;
+ }
+ }
} else {
- var dir = dirname(path);
- path = (dir !== '') ? dir + '/' + newpath : newpath;
+ buf.push(path[i]);
+ knownHards[buf.join('/')] = true;
}
- } else {
- return normalize(path);
}
}
- stats = null;
}
+ return buf.join('/');
}
-function readlinkDeep(path, stats, callback) {
- var seen_links = {}, resolved_link, file_id;
- function next(stats) {
- file_id = stats.dev.toString(32)+":"+stats.ino.toString(32);
- if (file_id in seen_links) {
- callback(new Error("cyclic symbolic link at "+path));
- } else {
- seen_links[file_id] = 1;
- if (stats.isSymbolicLink()) {
- exports.readlink(path, function(err, newpath) {
- if (err) callback(err);
- if (newpath.charAt(0) === '/') {
- path = newpath;
- } else {
- var dir = dirname(path);
- path = (dir !== '') ? dir + '/' + newpath : newpath;
- }
- _next();
- });
- } else {
- callback(null, normalize(path));
- }
+
+exports.realpath = function (path, callback) {
+ var seen_links = {}, knownHards = {}, buf = [''], i = 0, part, x;
+ if (path.charAt(0) !== '/') {
+ // assumes cwd is canonical
+ var cwd = process.cwd().split('/');
+ path = cwd.concat(path.split('/'));
+ path = normalizeArray(path);
+ i = cwd.length-1;
+ buf = [].concat(cwd);
+ } else {
+ path = normalizeArray(path.split('/'));
+ }
+ function done(err) {
+ if (callback) {
+ if (!err) callback(err, buf.join('/'));
+ else callback(err);
}
}
- function _next() {
- exports.lstat(path, function(err, stats){
- if (err) callback(err);
- else next(stats);
- });
+ function next() {
+ if (++i === path.length) return done();
+ part = path.slice(0, i+1).join('/');
+ if (part.length === 0) return next();
+ if (part in knownHards) {
+ buf.push(path[i]);
+ next();
+ } else {
+ exports.lstat(part, function(err, stats){
+ if (err) return done(err);
+ if (stats.isSymbolicLink()) {
+ x = stats.dev.toString(32)+":"+stats.ino.toString(32);
+ if (x in seen_links)
+ return done(new Error("cyclic link at "+part));
+ seen_links[x] = true;
+ exports.readlink(part, function(err, npart){
+ if (err) return done(err);
+ part = npart;
+ if (part.charAt(0) === '/') {
+ // absolute
+ path = normalizeArray(part.split('/'));
+ buf = [''];
+ i = 0;
+ } else {
+ // relative
+ Array.prototype.splice.apply(path, [i, 1].concat(part.split('/')));
+ part = normalizeArray(path);
+ var y = 0, L = Math.max(path.length, part.length), delta;
+ for (; y<L && path[y] === part[y]; y++);
+ if (y !== L) {
+ path = part;
+ delta = i-y;
+ i = y-1; // resolve new node if needed
+ if (delta > 0) buf.splice(y, delta);
+ }
+ else {
+ i--; // resolve new node if needed
+ }
+ }
+ next();
+ }); // fs.readlink
+ }
+ else {
+ buf.push(path[i]);
+ knownHards[buf.join('/')] = true;
+ next();
+ }
+ }); // fs.lstat
+ }
}
- if (stats) next(stats);
- else _next();
-}
-
-exports.realpathSync = function(path) {
- var stats = exports.lstatSync(path);
- if (stats.isSymbolicLink())
- return readlinkDeepSync(path, stats);
- else
- return normalize(path);
-}
-
-exports.realpath = function(path, callback) {
- var resolved_path = path;
- if (!callback) return;
- exports.lstat(path, function(err, stats){
- if (err)
- callback(err);
- else if (stats.isSymbolicLink())
- readlinkDeep(path, stats, callback);
- else
- callback(null, normalize(path));
- });
+ next();
}
+
+ exports.createReadStream = function(path, options) {
+ return new FileReadStream(path, options);
+ };
+
+ var FileReadStream = exports.FileReadStream = function(path, options) {
+ events.EventEmitter.call(this);
+
+ this.path = path;
+ this.fd = null;
+ this.readable = true;
+ this.paused = false;
+
+ this.flags = 'r';
+ this.encoding = 'binary';
+ this.mode = 0666;
+ this.bufferSize = 4 * 1024;
+
+ process.mixin(this, options || {});
+
+ var
+ self = this,
+ buffer = null;
+
+ function read() {
+ if (!self.readable || self.paused) {
+ return;
+ }
+
+ fs.read(self.fd, self.bufferSize, undefined, self.encoding, function(err, data, bytesRead) {
+ if (err) {
+ self.emit('error', err);
+ self.readable = false;
+ return;
+ }
+
+ if (bytesRead === 0) {
+ self.emit('end');
+ self.forceClose();
+ return;
+ }
+
+ // do not emit events if the stream is paused
+ if (self.paused) {
+ buffer = data;
+ return;
+ }
+
+ // do not emit events anymore after we declared the stream unreadable
+ if (!self.readable) {
+ return;
+ }
+
+ self.emit('data', data);
+ read();
+ });
+ }
+
+ fs.open(this.path, this.flags, this.mode, function(err, fd) {
+ if (err) {
+ self.emit('error', err);
+ self.readable = false;
+ return;
+ }
+
+ self.fd = fd;
+ self.emit('open', fd);
+ read();
+ });
+
+ this.forceClose = function() {
+ this.readable = false;
+ fs.close(this.fd, function(err) {
+ if (err) {
+ self.emit('error', err);
+ return;
+ }
+
+ self.emit('close');
+ });
+ };
+
+ this.pause = function() {
+ this.paused = true;
+ };
+
+ this.resume = function() {
+ this.paused = false;
+
+ if (buffer !== null) {
+ self.emit('data', buffer);
+ buffer = null;
+ }
+
+ read();
+ };
+ };
+ sys.inherits(FileReadStream, events.EventEmitter);
+
+ exports.createWriteStream = function(path, options) {
+ return new FileWriteStream(path, options);
+ };
+
+ var FileWriteStream = exports.FileWriteStream = function(path, options) {
+ events.EventEmitter.call(this);
+
+ this.path = path;
+ this.fd = null;
+ this.writeable = true;
+
+ this.flags = 'w';
+ this.encoding = 'binary';
+ this.mode = 0666;
+
+ process.mixin(this, options || {});
+
+ var
+ self = this,
+ queue = [],
+ busy = false;
+
+ queue.push([fs.open, this.path, this.flags, this.mode]);
+
+ function flush() {
+ if (busy) {
+ return;
+ }
+
+ var args = queue.shift();
+ if (!args) {
+ return self.emit('drain');
+ }
+
+ busy = true;
+
+ var method = args.shift();
+
+ args.push(function(err) {
+ busy = false;
+
+ if (err) {
+ self.writeable = false;
+ self.emit('error', err);
+ return;
+ }
+
+ // save reference for file pointer
+ if (method === fs.open) {
+ self.fd = arguments[1];
+ self.emit('open', self.fd);
+ }
+
+ // stop flushing after close
+ if (method === fs.close) {
+ self.emit('close');
+ return;
+ }
+
+ flush();
+ });
+
+ // Inject the file pointer
+ if (method !== fs.open) {
+ args.unshift(self.fd);
+ }
+
+ method.apply(null, args);
+ };
+
+ this.write = function(data) {
+ if (!this.writeable) {
+ throw new Error('stream not writeable');
+ }
+
+ queue.push([fs.write, data, undefined, this.encoding]);
+ flush();
+ return false;
+ };
+
+ this.close = function() {
+ this.writeable = false;
+ queue.push([fs.close,]);
+ flush();
+ };
+
+ this.forceClose = function() {
+ this.writeable = false;
+ fs.close(self.fd, function(err) {
+ if (err) {
+ self.emit('error', err);
+ return;
+ }
+
+ self.emit('close');
+ });
+ };
+
+ flush();
+ };
+ sys.inherits(FileWriteStream, events.EventEmitter);