ndir.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. /**
  2. * Module dependencies.
  3. */
  4. var fs = require('fs');
  5. var path = require('path');
  6. var util = require('util');
  7. var EventEmitter = require('events').EventEmitter;
  8. /**
  9. * walk dir base on Event
  10. *
  11. * - `dir` Event: when a dir walk through, emit('dir', dirpath, files)
  12. * - `end` Event: when a dir walk over, emit('end')
  13. * - `error` Event: when stat a path error, emit('error', err, path)
  14. */
  15. exports.walk = function walk(dir, onDir, onEnd, onError) {
  16. return new Walk(dir, onDir, onEnd, onError);
  17. };
  18. function Walk(root, onDir, onEnd, onError) {
  19. if (!(this instanceof Walk)) {
  20. return new Walk(root, onDir, onEnd, onError);
  21. }
  22. this.dirs = [path.resolve(root)];
  23. onDir && this.on('dir', onDir);
  24. onEnd && this.on('end', onEnd);
  25. onError && this.on('error', onError);
  26. var self = this;
  27. // let listen `files` Event first.
  28. process.nextTick(function() {
  29. self.next();
  30. });
  31. };
  32. util.inherits(Walk, EventEmitter);
  33. exports.Walk = Walk;
  34. Walk.prototype.next = function() {
  35. var dir = this.dirs.shift();
  36. if (!dir) {
  37. return this.emit('end');
  38. }
  39. this._dir(dir);
  40. };
  41. Walk.prototype._dir = function(dir) {
  42. var self = this;
  43. fs.readdir(dir, function(err, files) {
  44. if (err) {
  45. self.emit('error', err, dir);
  46. return self.next();
  47. }
  48. var infos = [];
  49. if (files.length === 0) {
  50. self.emit('dir', dir, infos);
  51. return self.next();
  52. }
  53. var counter = 0;
  54. files.forEach(function(file) {
  55. var p = path.join(dir, file);
  56. fs.lstat(p, function(err, stats) {
  57. if (err) {
  58. self.emit('error', err, p);
  59. } else {
  60. infos.push([p, stats]);
  61. if (stats.isDirectory()) {
  62. self.dirs.push(p);
  63. }
  64. }
  65. if (++counter === files.length) {
  66. self.emit('dir', dir, infos);
  67. self.next();
  68. }
  69. });
  70. });
  71. });
  72. };
  73. /**
  74. * Copy file, auto create tofile dir if dir not exists.
  75. */
  76. exports.copyfile = function copyfile(fromfile, tofile, callback) {
  77. fromfile = path.resolve(fromfile);
  78. tofile = path.resolve(tofile);
  79. if (fromfile === tofile) {
  80. var msg = 'cp: "' + fromfile + '" and "' + tofile + '" are identical (not copied).';
  81. return callback(new Error(msg));
  82. }
  83. exports.mkdir(path.dirname(tofile), function(err) {
  84. if (err) {
  85. return callback(err);
  86. }
  87. var ws = fs.createWriteStream(tofile);
  88. var rs = fs.createReadStream(fromfile);
  89. var error = null;
  90. var onerr = function(err) {
  91. var cb = callback;
  92. callback = null;
  93. cb(err);
  94. };
  95. ws.on('error', onerr); // if file not open, these is only error event will be emit.
  96. rs.on('error', onerr);
  97. ws.on('close', function() {
  98. // after file open, error event could be fire close event before.
  99. callback && callback();
  100. });
  101. rs.pipe(ws);
  102. });
  103. };
  104. function _mkdir(dir, callback) {
  105. path.exists(dir, function(exists) {
  106. if (exists) {
  107. return callback();
  108. }
  109. fs.mkdir(dir, callback);
  110. });
  111. };
  112. /**
  113. * mkdir if dir not exists, equal mkdir -p /path/foo/bar
  114. *
  115. * @param {String} dir
  116. * @param {Function} callback
  117. */
  118. exports.mkdir = function mkdir(dir, callback) {
  119. var parent = path.dirname(dir);
  120. path.exists(parent, function(exists) {
  121. if (exists) {
  122. return _mkdir(dir, callback);
  123. }
  124. exports.mkdir(parent, function(err) {
  125. if (err) {
  126. return callback(err);
  127. }
  128. _mkdir(dir, callback);
  129. });
  130. });
  131. };
  132. /**
  133. * Line data reader
  134. *
  135. * ndir.createLineReader('/tmp/access.log')
  136. * .on('line', function(line) { console.log(line.toString()); })
  137. * .on('end', function() {})
  138. * .on('error', function(err) { console.error(err); });
  139. *
  140. * @param {String|ReadStream} file, file path or a `ReadStream` object.
  141. */
  142. exports.createLineReader = function(file) {
  143. return new LineReader(file);
  144. };
  145. function LineReader(file) {
  146. if (typeof file === 'string') {
  147. this.readstream = fs.createReadStream(file);
  148. } else {
  149. this.readstream = file;
  150. }
  151. this.remainBuffers = [];
  152. var self = this;
  153. this.readstream.on('data', function(data) {
  154. self.ondata(data);
  155. });
  156. this.readstream.on('error', function(err) {
  157. self.emit('error', err);
  158. });
  159. this.readstream.on('end', function() {
  160. self.emit('end');
  161. });
  162. }
  163. util.inherits(LineReader, EventEmitter);
  164. LineReader.prototype.ondata = function(data) {
  165. var i = 0;
  166. var found = false;
  167. for (var l = data.length; i < l; i++) {
  168. if (data[i] === 10) {
  169. found = true;
  170. break;
  171. }
  172. }
  173. if (!found) {
  174. this.remainBuffers.push(data);
  175. return;
  176. }
  177. var line = null;
  178. if (this.remainBuffers.length > 0) {
  179. var size = i;
  180. for (var j = 0, jl = this.remainBuffers.length; j < jl; j++) {
  181. size += this.remainBuffers[j].length;
  182. }
  183. line = new Buffer(size);
  184. var pos = 0;
  185. for (var j = 0, jl = this.remainBuffers.length; j < jl; j++) {
  186. var buf = this.remainBuffers[j];
  187. buf.copy(line, pos);
  188. pos += buf.length;
  189. }
  190. // check if `\n` is the first char in `data`
  191. if (i > 0) {
  192. data.copy(line, pos, 0, i);
  193. }
  194. this.remainBuffers = [];
  195. } else {
  196. line = data.slice(0, i);
  197. }
  198. this.emit('line', line);
  199. this.ondata(data.slice(i + 1));
  200. };