main.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Copyright 2010-2011 Mikeal Rogers
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. var sys = require('util')
  15. , fs = require('fs')
  16. , path = require('path')
  17. , events = require('events')
  18. ;
  19. function walk (dir, options, callback) {
  20. if (!callback) {callback = options; options = {}}
  21. if (!callback.files) callback.files = {};
  22. if (!callback.pending) callback.pending = 0;
  23. callback.pending += 1;
  24. fs.stat(dir, function (err, stat) {
  25. if (err) return callback(err);
  26. callback.files[dir] = stat;
  27. fs.readdir(dir, function (err, files) {
  28. if (err) {
  29. if(err.code === 'EACCES' && options.ignoreUnreadableDir) return callback();
  30. return callback(err);
  31. }
  32. callback.pending -= 1;
  33. files.forEach(function (f, index) {
  34. f = path.join(dir, f);
  35. callback.pending += 1;
  36. fs.stat(f, function (err, stat) {
  37. var enoent = false
  38. , done = false;
  39. if (err) {
  40. if (err.code !== 'ENOENT' && (err.code !== 'EPERM' && options.ignoreNotPermitted)) {
  41. return callback(err);
  42. } else {
  43. enoent = true;
  44. }
  45. }
  46. callback.pending -= 1;
  47. done = callback.pending === 0;
  48. if (!enoent) {
  49. if (options.ignoreDotFiles && path.basename(f)[0] === '.') return done && callback(null, callback.files);
  50. if (options.filter && !options.filter(f, stat)) return done && callback(null, callback.files);
  51. callback.files[f] = stat;
  52. if (stat.isDirectory() && !(options.ignoreDirectoryPattern && options.ignoreDirectoryPattern.test(f))) walk(f, options, callback);
  53. done = callback.pending === 0;
  54. if (done) callback(null, callback.files);
  55. }
  56. })
  57. })
  58. if (callback.pending === 0) callback(null, callback.files);
  59. })
  60. if (callback.pending === 0) callback(null, callback.files);
  61. })
  62. }
  63. var watchedFiles = Object.create(null);
  64. exports.watchTree = function ( root, options, callback ) {
  65. if (!callback) {callback = options; options = {}}
  66. walk(root, options, function (err, files) {
  67. if (err) throw err;
  68. var fileWatcher = function (f) {
  69. var fsOptions = {};
  70. if (options.interval) {
  71. fsOptions.interval = options.interval * 1000;
  72. }
  73. fs.watchFile(f, fsOptions, function (c, p) {
  74. // Check if anything actually changed in stat
  75. if (files[f] && !files[f].isDirectory() && c.nlink !== 0 && files[f].mtime.getTime() == c.mtime.getTime()) return;
  76. files[f] = c;
  77. if (!files[f].isDirectory()) callback(f, c, p);
  78. else {
  79. fs.readdir(f, function (err, nfiles) {
  80. if (err) return;
  81. nfiles.forEach(function (b) {
  82. var file = path.join(f, b);
  83. if (!files[file] && (options.ignoreDotFiles !== true || b[0] != '.')) {
  84. fs.stat(file, function (err, stat) {
  85. if (options.filter && !options.filter(file, stat)) return;
  86. callback(file, stat, null);
  87. files[file] = stat;
  88. fileWatcher(file);
  89. })
  90. }
  91. })
  92. })
  93. }
  94. if (c.nlink === 0) {
  95. // unwatch removed files.
  96. delete files[f]
  97. fs.unwatchFile(f);
  98. }
  99. })
  100. }
  101. fileWatcher(root);
  102. for (var i in files) {
  103. fileWatcher(i);
  104. }
  105. watchedFiles[root] = files;
  106. callback(files, null, null);
  107. })
  108. }
  109. exports.unwatchTree = function (root) {
  110. if (!watchedFiles[root]) return;
  111. Object.keys(watchedFiles[root]).forEach(fs.unwatchFile);
  112. watchedFiles[root] = false;
  113. };
  114. exports.createMonitor = function (root, options, cb) {
  115. if (!cb) {cb = options; options = {}}
  116. var monitor = new events.EventEmitter();
  117. monitor.stop = exports.unwatchTree.bind(null, root);
  118. var prevFile = {file: null,action: null,stat: null};
  119. exports.watchTree(root, options, function (f, curr, prev) {
  120. // if not curr, prev, but f is an object
  121. if (typeof f == "object" && prev == null && curr === null) {
  122. monitor.files = f;
  123. return cb(monitor);
  124. }
  125. // if not prev and either prevFile.file is not f or prevFile.action is not created
  126. if (!prev) {
  127. if (prevFile.file != f || prevFile.action != "created") {
  128. prevFile = { file: f, action: "created", stat: curr };
  129. return monitor.emit("created", f, curr);
  130. }
  131. }
  132. // if curr.nlink is 0 and either prevFile.file is not f or prevFile.action is not removed
  133. if (curr) {
  134. if (curr.nlink === 0) {
  135. if (prevFile.file != f || prevFile.action != "removed") {
  136. prevFile = { file: f, action: "removed", stat: curr };
  137. return monitor.emit("removed", f, curr);
  138. }
  139. }
  140. }
  141. // if prevFile.file is null or prevFile.stat.mtime is not the same as curr.mtime
  142. if (prevFile.file === null) {
  143. return monitor.emit("changed", f, curr, prev);
  144. }
  145. // stat might return null, so catch errors
  146. try {
  147. if (prevFile.stat.mtime.getTime() !== curr.mtime.getTime()) {
  148. return monitor.emit("changed", f, curr, prev);
  149. }
  150. } catch(e) {
  151. return monitor.emit("changed", f, curr, prev);
  152. }
  153. })
  154. }
  155. exports.walk = walk;