ndir.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*!
  2. * ndir - lib/ndir.js
  3. * Copyright(c) 2012 fengmk2 <fengmk2@gmail.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var fs = require('fs');
  10. var path = require('path');
  11. var util = require('util');
  12. var EventEmitter = require('events').EventEmitter;
  13. fs.exists = fs.exists || path.exists;
  14. /**
  15. * dir Walker Class.
  16. *
  17. * @constructor
  18. * @param {String} root Root path.
  19. * @param {Function(dirpath, files)} [onDir] The `dir` event callback.
  20. * @param {Function} [onEnd] The `end` event callback.
  21. * @param {Function(err)} [onError] The `error` event callback.
  22. * @public
  23. */
  24. function Walk(root, onDir, onEnd, onError) {
  25. if (!(this instanceof Walk)) {
  26. return new Walk(root, onDir, onEnd, onError);
  27. }
  28. this.dirs = [path.resolve(root)];
  29. if (onDir) {
  30. this.on('dir', onDir);
  31. }
  32. if (onEnd) {
  33. this.on('end', onEnd);
  34. }
  35. onError && this.on('error', onError);
  36. var self = this;
  37. // let listen `files` Event first.
  38. process.nextTick(function () {
  39. self.next();
  40. });
  41. }
  42. util.inherits(Walk, EventEmitter);
  43. exports.Walk = Walk;
  44. /**
  45. * Walking dir base on `Event`.
  46. *
  47. * @param {String} dir Start walking path.
  48. * @param {Function(dir)} [onDir] When a dir walk through, `emit('dir', dirpath, files)`.
  49. * @param {Function} [onEnd] When a dir walk over, `emit('end')`.
  50. * @param {Function(err)} [onError] When stat a path error, `emit('error', err, path)`.
  51. * @return {Walk} dir walker instance.
  52. * @public
  53. */
  54. exports.walk = function walk(dir, onDir, onEnd, onError) {
  55. return new Walk(dir, onDir, onEnd, onError);
  56. };
  57. /**
  58. * Next move, if move to the end,
  59. * will `emit('end')` event.
  60. *
  61. * @private
  62. */
  63. Walk.prototype.next = function () {
  64. var dir = this.dirs.shift();
  65. if (!dir) {
  66. return this.emit('end');
  67. }
  68. this._dir(dir);
  69. };
  70. /**
  71. * @private
  72. */
  73. Walk.prototype._dir = function (dir) {
  74. var self = this;
  75. fs.readdir(dir, function (err, files) {
  76. if (err) {
  77. self.emit('error', err, dir);
  78. return self.next();
  79. }
  80. var infos = [];
  81. if (files.length === 0) {
  82. self.emit('dir', dir, infos);
  83. return self.next();
  84. }
  85. var counter = 0;
  86. files.forEach(function (file) {
  87. var p = path.join(dir, file);
  88. fs.lstat(p, function (err, stats) {
  89. counter++;
  90. if (err) {
  91. self.emit('error', err, p);
  92. } else {
  93. infos.push([p, stats]);
  94. if (stats.isDirectory()) {
  95. self.dirs.push(p);
  96. }
  97. }
  98. if (counter === files.length) {
  99. self.emit('dir', dir, infos);
  100. self.next();
  101. }
  102. });
  103. });
  104. });
  105. };
  106. /**
  107. * Copy file, auto create tofile dir if dir not exists.
  108. *
  109. * @param {String} fromfile, Source file path.
  110. * @param {String} tofile, Target file path.
  111. * @param {Function(err)} callback
  112. * @public
  113. */
  114. exports.copyfile = function copyfile(fromfile, tofile, callback) {
  115. fromfile = path.resolve(fromfile);
  116. tofile = path.resolve(tofile);
  117. if (fromfile === tofile) {
  118. var msg = 'cp: "' + fromfile + '" and "' + tofile + '" are identical (not copied).';
  119. return callback(new Error(msg));
  120. }
  121. exports.mkdir(path.dirname(tofile), function (err) {
  122. if (err) {
  123. return callback(err);
  124. }
  125. var ws = fs.createWriteStream(tofile);
  126. var rs = fs.createReadStream(fromfile);
  127. var onerr = function (err) {
  128. callback && callback(err);
  129. callback = null;
  130. };
  131. ws.once('error', onerr); // if file not open, these is only error event will be emit.
  132. rs.once('error', onerr);
  133. ws.on('close', function () {
  134. // after file open, error event could be fire close event before.
  135. callback && callback();
  136. callback = null;
  137. });
  138. rs.pipe(ws);
  139. });
  140. };
  141. /**
  142. * @private
  143. */
  144. function _mkdir(dir, mode, callback) {
  145. fs.exists(dir, function (exists) {
  146. if (exists) {
  147. return callback();
  148. }
  149. fs.mkdir(dir, mode, callback);
  150. });
  151. }
  152. /**
  153. * mkdir if dir not exists, equal mkdir -p /path/foo/bar
  154. *
  155. * @param {String} dir
  156. * @param {Number} [mode] file mode, default is 0777.
  157. * @param {Function(err)} callback
  158. * @public
  159. */
  160. exports.mkdir = function mkdir(dir, mode, callback) {
  161. if (typeof mode === 'function') {
  162. callback = mode;
  163. mode = 0777 & (~process.umask());
  164. }
  165. var parent = path.dirname(dir);
  166. fs.exists(parent, function (exists) {
  167. if (exists) {
  168. return _mkdir(dir, mode, callback);
  169. }
  170. exports.mkdir(parent, mode, function (err) {
  171. if (err) {
  172. return callback(err);
  173. }
  174. _mkdir(dir, mode, callback);
  175. });
  176. });
  177. };
  178. exports.mkdirp = exports.mkdir;
  179. /**
  180. * Read stream data line by line.
  181. *
  182. * @constructor
  183. * @param {String|ReadStream} file File path or data stream object.
  184. */
  185. function LineReader(file) {
  186. if (typeof file === 'string') {
  187. this.readstream = fs.createReadStream(file);
  188. } else {
  189. this.readstream = file;
  190. }
  191. this.remainBuffers = [];
  192. var self = this;
  193. this.readstream.on('data', function (data) {
  194. self.ondata(data);
  195. });
  196. this.readstream.on('error', function (err) {
  197. self.emit('error', err);
  198. });
  199. this.readstream.on('end', function () {
  200. self.emit('end');
  201. });
  202. }
  203. util.inherits(LineReader, EventEmitter);
  204. /**
  205. * `Stream` data event handler.
  206. *
  207. * @param {Buffer} data
  208. * @private
  209. */
  210. LineReader.prototype.ondata = function (data) {
  211. var i = 0;
  212. var found = false;
  213. for (var l = data.length; i < l; i++) {
  214. if (data[i] === 10) {
  215. found = true;
  216. break;
  217. }
  218. }
  219. if (!found) {
  220. this.remainBuffers.push(data);
  221. return;
  222. }
  223. var line = null;
  224. if (this.remainBuffers.length > 0) {
  225. var size = i;
  226. var j, jl = this.remainBuffers.length;
  227. for (j = 0; j < jl; j++) {
  228. size += this.remainBuffers[j].length;
  229. }
  230. line = new Buffer(size);
  231. var pos = 0;
  232. for (j = 0; j < jl; j++) {
  233. var buf = this.remainBuffers[j];
  234. buf.copy(line, pos);
  235. pos += buf.length;
  236. }
  237. // check if `\n` is the first char in `data`
  238. if (i > 0) {
  239. data.copy(line, pos, 0, i);
  240. }
  241. this.remainBuffers = [];
  242. } else {
  243. line = data.slice(0, i);
  244. }
  245. this.emit('line', line);
  246. this.ondata(data.slice(i + 1));
  247. };
  248. /**
  249. * Line data reader
  250. *
  251. * @example
  252. * ```
  253. * var ndir = require('ndir');
  254. * ndir.createLineReader('/tmp/access.log')
  255. * .on('line', function (line) {
  256. * console.log(line.toString());
  257. * })
  258. * .on('end', function () {
  259. * console.log('end');
  260. * })
  261. * .on('error', function (err) {
  262. * console.error(err);
  263. * });
  264. * ```
  265. *
  266. * @param {String|ReadStream} file, file path or a `ReadStream` object.
  267. */
  268. exports.createLineReader = function (file) {
  269. return new LineReader(file);
  270. };