Merge remote branch 'felixge/file2'
authorRyan Dahl <ry@tinyclouds.org>
Fri, 5 Mar 2010 19:11:23 +0000 (11:11 -0800)
committerRyan Dahl <ry@tinyclouds.org>
Fri, 5 Mar 2010 19:11:23 +0000 (11:11 -0800)
1  2 
doc/api.txt
lib/fs.js

diff --cc doc/api.txt
Simple merge
diff --cc lib/fs.js
+++ b/lib/fs.js
@@@ -292,132 -296,286 +296,332 @@@ exports.unwatchFile = function (filenam
  // 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);