// 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);