ndir
Version:
The lost dir util tools for Nodejs. Handle dir and file in Event
284 lines (270 loc) • 6.61 kB
JavaScript
/*!
* ndir - lib/ndir.js
* Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var fs = require('fs');
var path = require('path');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
fs.exists = fs.exists || path.exists;
/**
* dir Walker Class.
*
* @constructor
* @param {String} root Root path.
* @param {Function(dirpath, files)} [onDir] The `dir` event callback.
* @param {Function} [onEnd] The `end` event callback.
* @param {Function(err)} [onError] The `error` event callback.
* @public
*/
function Walk(root, onDir, onEnd, onError) {
if (!(this instanceof Walk)) {
return new Walk(root, onDir, onEnd, onError);
}
this.dirs = [path.resolve(root)];
if (onDir) {
this.on('dir', onDir);
}
if (onEnd) {
this.on('end', onEnd);
}
onError && this.on('error', onError);
var self = this;
// let listen `files` Event first.
process.nextTick(function () {
self.next();
});
}
util.inherits(Walk, EventEmitter);
exports.Walk = Walk;
/**
* Walking dir base on `Event`.
*
* @param {String} dir Start walking path.
* @param {Function(dir)} [onDir] When a dir walk through, `emit('dir', dirpath, files)`.
* @param {Function} [onEnd] When a dir walk over, `emit('end')`.
* @param {Function(err)} [onError] When stat a path error, `emit('error', err, path)`.
* @return {Walk} dir walker instance.
* @public
*/
exports.walk = function walk(dir, onDir, onEnd, onError) {
return new Walk(dir, onDir, onEnd, onError);
};
/**
* Next move, if move to the end,
* will `emit('end')` event.
*
* @private
*/
Walk.prototype.next = function () {
var dir = this.dirs.shift();
if (!dir) {
return this.emit('end');
}
this._dir(dir);
};
/**
* @private
*/
Walk.prototype._dir = function (dir) {
var self = this;
fs.readdir(dir, function (err, files) {
if (err) {
self.emit('error', err, dir);
return self.next();
}
var infos = [];
if (files.length === 0) {
self.emit('dir', dir, infos);
return self.next();
}
var counter = 0;
files.forEach(function (file) {
var p = path.join(dir, file);
fs.lstat(p, function (err, stats) {
counter++;
if (err) {
self.emit('error', err, p);
} else {
infos.push([p, stats]);
if (stats.isDirectory()) {
self.dirs.push(p);
}
}
if (counter === files.length) {
self.emit('dir', dir, infos);
self.next();
}
});
});
});
};
/**
* Copy file, auto create tofile dir if dir not exists.
*
* @param {String} fromfile, Source file path.
* @param {String} tofile, Target file path.
* @param {Function(err)} callback
* @public
*/
exports.copyfile = function copyfile(fromfile, tofile, callback) {
fromfile = path.resolve(fromfile);
tofile = path.resolve(tofile);
if (fromfile === tofile) {
var msg = 'cp: "' + fromfile + '" and "' + tofile + '" are identical (not copied).';
return callback(new Error(msg));
}
exports.mkdir(path.dirname(tofile), function (err) {
if (err) {
return callback(err);
}
var ws = fs.createWriteStream(tofile);
var rs = fs.createReadStream(fromfile);
var onerr = function (err) {
callback && callback(err);
callback = null;
};
ws.once('error', onerr); // if file not open, these is only error event will be emit.
rs.once('error', onerr);
ws.on('close', function () {
// after file open, error event could be fire close event before.
callback && callback();
callback = null;
});
rs.pipe(ws);
});
};
/**
* @private
*/
function _mkdir(dir, mode, callback) {
fs.exists(dir, function (exists) {
if (exists) {
return callback();
}
fs.mkdir(dir, mode, callback);
});
}
/**
* mkdir if dir not exists, equal mkdir -p /path/foo/bar
*
* @param {String} dir
* @param {Number} [mode] file mode, default is 0777.
* @param {Function(err)} callback
* @public
*/
exports.mkdir = function mkdir(dir, mode, callback) {
if (typeof mode === 'function') {
callback = mode;
mode = 0777 & (~process.umask());
}
var parent = path.dirname(dir);
fs.exists(parent, function (exists) {
if (exists) {
return _mkdir(dir, mode, callback);
}
exports.mkdir(parent, mode, function (err) {
if (err) {
return callback(err);
}
_mkdir(dir, mode, callback);
});
});
};
exports.mkdirp = exports.mkdir;
/**
* Read stream data line by line.
*
* @constructor
* @param {String|ReadStream} file File path or data stream object.
*/
function LineReader(file) {
if (typeof file === 'string') {
this.readstream = fs.createReadStream(file);
} else {
this.readstream = file;
}
this.remainBuffers = [];
var self = this;
this.readstream.on('data', function (data) {
self.ondata(data);
});
this.readstream.on('error', function (err) {
self.emit('error', err);
});
this.readstream.on('end', function () {
self.emit('end');
});
}
util.inherits(LineReader, EventEmitter);
/**
* `Stream` data event handler.
*
* @param {Buffer} data
* @private
*/
LineReader.prototype.ondata = function (data) {
var i = 0;
var found = false;
for (var l = data.length; i < l; i++) {
if (data[i] === 10) {
found = true;
break;
}
}
if (!found) {
this.remainBuffers.push(data);
return;
}
var line = null;
if (this.remainBuffers.length > 0) {
var size = i;
var j, jl = this.remainBuffers.length;
for (j = 0; j < jl; j++) {
size += this.remainBuffers[j].length;
}
line = new Buffer(size);
var pos = 0;
for (j = 0; j < jl; j++) {
var buf = this.remainBuffers[j];
buf.copy(line, pos);
pos += buf.length;
}
// check if `\n` is the first char in `data`
if (i > 0) {
data.copy(line, pos, 0, i);
}
this.remainBuffers = [];
} else {
line = data.slice(0, i);
}
this.emit('line', line);
this.ondata(data.slice(i + 1));
};
/**
* Line data reader
*
* @example
* ```
* var ndir = require('ndir');
* ndir.createLineReader('/tmp/access.log')
* .on('line', function (line) {
* console.log(line.toString());
* })
* .on('end', function () {
* console.log('end');
* })
* .on('error', function (err) {
* console.error(err);
* });
* ```
*
* @param {String|ReadStream} file, file path or a `ReadStream` object.
*/
exports.createLineReader = function (file) {
return new LineReader(file);
};