/*! * ndir - lib/ndir.js * Copyright(c) 2012 fengmk2 * 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); };