diff --git a/README.md b/README.md index 269158b1..230aa71e 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ watcher .on('unlink', function(path) {console.log('File', path, 'has been removed');}) .on('unlinkDir', function(path) {console.log('Directory', path, 'has been removed');}) .on('error', function(error) {console.error('Error happened', error);}) + .on('ready', function() {console.log('Files has been added and ready to detect changes')}) // 'add', 'addDir' and 'change' events also receive stat() results as second argument. // http://nodejs.org/api/fs.html#fs_class_fs_stats diff --git a/index.js b/index.js index 090fd297..b9a59a99 100644 --- a/index.js +++ b/index.js @@ -281,10 +281,10 @@ FSWatcher.prototype._handleFile = function(file, stats, initialAdd) { // directory - string, fs path. // Returns nothing. -FSWatcher.prototype._handleDir = function(directory, stats, initialAdd) { +FSWatcher.prototype._handleDir = function(directory, stats, initialAdd, readyCallback) { var read, _this = this; - read = function(directory, initialAdd) { + read = function(directory, initialAdd, readyCallback) { return fs.readdir(directory, function(error, current) { var previous; if (error != null) { @@ -305,17 +305,20 @@ FSWatcher.prototype._handleDir = function(directory, stats, initialAdd) { return _this._remove(directory, file); }); + var c = 0; // Files that present in current directory snapshot // but absent in previous are added to watch list and // emit `add` event. current.filter(function(file) { return previous.indexOf(file) === -1; - }).forEach(function(file) { - _this._handle(sysPath.join(directory, file), initialAdd); + }).forEach(function(file, i, arr) { + _this._handle(sysPath.join(directory, file), initialAdd, function() { + if (++c === arr.length && readyCallback) readyCallback(); + }); }); }); }; - read(directory, initialAdd); + read(directory, initialAdd, readyCallback); this._watch(directory, function(dir) { return read(dir, false); }); @@ -330,13 +333,14 @@ FSWatcher.prototype._handleDir = function(directory, stats, initialAdd) { // item - string, path to file or directory. // Returns nothing. -FSWatcher.prototype._handle = function(item, initialAdd) { +FSWatcher.prototype._handle = function(item, initialAdd, readyCallback) { var _this = this; if (this._isIgnored(item)) { return; } return fs.realpath(item, function(error, path) { if (error && error.code === 'ENOENT') { + readyCallback(); return; } if (error != null) { @@ -347,16 +351,19 @@ FSWatcher.prototype._handle = function(item, initialAdd) { return _this.emit('error', error); } if (_this.options.ignorePermissionErrors && (!_this._hasReadPermissions(stats))) { + readyCallback(); return; } if (_this._isIgnored.length === 2 && _this._isIgnored(item, stats)) { + readyCallback(); return; } if (stats.isFile()) { _this._handleFile(item, stats, initialAdd); + readyCallback(); } if (stats.isDirectory()) { - return _this._handleDir(item, stats, initialAdd); + return _this._handleDir(item, stats, initialAdd, readyCallback); } }); }); @@ -371,32 +378,40 @@ FSWatcher.prototype.emit = function() { } }; -FSWatcher.prototype._addToFsEvents = function(files) { +FSWatcher.prototype._addToFsEvents = function(files, readyCallback) { var handle, - _this = this; + _this = this, + c = 0, + fileCount = files.length; + handle = function(path) { - return _this.emit('add', path); + if (_this.options.ignoreInitial) { + _this.emit('add', path); + } + if (++c === fileCount) { + readyCallback(); + } }; files.forEach(function(file) { - if (!_this.options.ignoreInitial) { - fs.stat(file, function(error, stats) { - if (error != null) { - return _this.emit('error', error); - } - if (stats.isDirectory()) { - return recursiveReaddir(file, function(error, dirFiles) { - if (error != null) { - return _this.emit('error', error); - } - return dirFiles.filter(function(path) { - return !_this._isIgnored(path); - }).forEach(handle); + fs.stat(file, function(error, stats) { + if (error != null) { + return _this.emit('error', error); + } + if (stats.isDirectory()) { + return recursiveReaddir(file, function(error, dirFiles) { + if (error != null) { + return _this.emit('error', error); + } + var dirFiles = dirFiles.filter(function(path) { + return !_this._isIgnored(path); }); - } else { - return handle(file); - } - }); - } + fileCount += dirFiles.length - 1; + dirFiles.forEach(handle); + }); + } else { + return handle(file); + } + }); return _this._watchWithFsEvents(file); }); return this; @@ -416,10 +431,13 @@ FSWatcher.prototype.add = function(files) { if (this._initialAdd == null) this._initialAdd = true; if (!Array.isArray(files)) files = [files]; - if (this.options.useFsEvents) return this._addToFsEvents(files); + if (this.options.useFsEvents) return this._addToFsEvents(files, this.emit.bind(this, 'ready')); + var c = 0; files.forEach(function(file) { - return _this._handle(file, _this._initialAdd); + _this._handle(file, _this._initialAdd, function() { + if (++c === files.length) _this.emit('ready'); + }); }); this._initialAdd = false; return this; diff --git a/test.js b/test.js index db01ee9e..24bbf777 100644 --- a/test.js +++ b/test.js @@ -1,6 +1,6 @@ 'use strict'; -var chai, chokidar, delay, expect, fixturesPath, fs, getFixturePath, isBinary, should, sinon, sysPath; +var chai, chokidar, delay, expect, fixturesPath, fs, getFixturePath, isBinary, should, sinon, sysPath, os; chai = require('chai'); @@ -18,6 +18,8 @@ fs = require('fs'); sysPath = require('path'); +os = require('os'); + getFixturePath = function(subPath) { return sysPath.join(__dirname, 'test-fixtures', subPath); }; @@ -182,7 +184,7 @@ describe('chokidar', function() { }); }); }); - return describe('watch options', function() { + describe('watch options', function() { return describe('ignoreInitial', function() { var options; options = { @@ -279,6 +281,43 @@ describe('chokidar', function() { }); }); }); + describe('ready event', function() { + var tmpdir = os.tmpdir(); + var testdir = sysPath.join(tmpdir, 'chokidar_test'); + var subdir = sysPath.join(testdir, 'test_sub') + beforeEach(function() { + try { + fs.mkdirSync(testdir); + } catch (e) { + //ignore. + } + try { + fs.mkdirSync(subdir); + } catch (e) { + //ignore. + } + for (var i = 0; i < 50; i++) { + fs.writeFileSync(sysPath.join(testdir, i + ''), 'test_' + i); + } + for (var i = 0; i < 50; i++) { + fs.writeFileSync(sysPath.join(subdir, i + ''), 'test_sub_' + i); + } + }); + + afterEach(function() { + this.watcher.close(); + }); + + it('should emit `ready` event when finished watching', function(done) { + this.watcher = chokidar.watch(testdir); + this.watcher.on('ready', done); + }); + + it('should emit `ready` with fsEvents', function(done) { + this.watcher = chokidar.watch(testdir, {usePolling: false}); + this.watcher.on('ready', done); + }); + }); }); describe('is-binary', function() {