react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / node_modules / commoner / lib / watcher.js
81159 viewsvar assert = require("assert");1var path = require("path");2var fs = require("graceful-fs");3var spawn = require("child_process").spawn;4var Q = require("q");5var EventEmitter = require("events").EventEmitter;6var ReadFileCache = require("./cache").ReadFileCache;7var util = require("./util");8var hasOwn = Object.prototype.hasOwnProperty;910function Watcher(readFileCache, persistent) {11assert.ok(this instanceof Watcher);12assert.ok(this instanceof EventEmitter);13assert.ok(readFileCache instanceof ReadFileCache);1415// During tests (and only during tests), persistent === false so that16// the test suite can actually finish and exit.17if (typeof persistent === "undefined") {18persistent = true;19}2021EventEmitter.call(this);2223var self = this;24var sourceDir = readFileCache.sourceDir;25var dirWatcher = new DirWatcher(sourceDir, persistent);2627Object.defineProperties(self, {28sourceDir: { value: sourceDir },29readFileCache: { value: readFileCache },30dirWatcher: { value: dirWatcher }31});3233// Watch everything the readFileCache already knows about, and any new34// files added in the future.35readFileCache.subscribe(function(relativePath) {36self.watch(relativePath);37});3839readFileCache.on("changed", function(relativePath) {40self.emit("changed", relativePath);41});4243function handleDirEvent(event, relativePath) {44if (self.dirWatcher.ready) {45self.getFileHandler(relativePath)(event);46}47}4849dirWatcher.on("added", function(relativePath) {50handleDirEvent("added", relativePath);51}).on("deleted", function(relativePath) {52handleDirEvent("deleted", relativePath);53}).on("changed", function(relativePath) {54handleDirEvent("changed", relativePath);55});56}5758util.inherits(Watcher, EventEmitter);59var Wp = Watcher.prototype;6061Wp.watch = function(relativePath) {62this.dirWatcher.add(path.dirname(path.join(63this.sourceDir, relativePath)));64};6566Wp.readFileP = function(relativePath) {67return this.readFileCache.readFileP(relativePath);68};6970Wp.noCacheReadFileP = function(relativePath) {71return this.readFileCache.noCacheReadFileP(relativePath);72};7374Wp.getFileHandler = util.cachedMethod(function(relativePath) {75var self = this;76return function handler(event) {77self.readFileCache.reportPossiblyChanged(relativePath);78};79});8081function orNull(err) {82return null;83}8485Wp.close = function() {86this.dirWatcher.close();87};8889/**90* DirWatcher code adapted from Jeffrey Lin's original implementation:91* https://github.com/jeffreylin/jsx_transformer_fun/blob/master/dirWatcher.js92*93* Invariant: this only watches the dir inode, not the actual path.94* That means the dir can't be renamed and swapped with another dir.95*/96function DirWatcher(inputPath, persistent) {97assert.ok(this instanceof DirWatcher);9899var self = this;100var absPath = path.resolve(inputPath);101102if (!fs.statSync(absPath).isDirectory()) {103throw new Error(inputPath + "is not a directory!");104}105106EventEmitter.call(self);107108self.ready = false;109self.on("ready", function(){110self.ready = true;111});112113Object.defineProperties(self, {114// Map of absDirPaths to fs.FSWatcher objects from fs.watch().115watchers: { value: {} },116dirContents: { value: {} },117rootPath: { value: absPath },118persistent: { value: !!persistent }119});120121process.nextTick(function() {122self.add(absPath);123self.emit("ready");124});125}126127util.inherits(DirWatcher, EventEmitter);128var DWp = DirWatcher.prototype;129130DWp.add = function(absDirPath) {131var self = this;132if (hasOwn.call(self.watchers, absDirPath)) {133return;134}135136self.watchers[absDirPath] = fs.watch(absDirPath, {137persistent: this.persistent138}).on("change", function(event, filename) {139self.updateDirContents(absDirPath, event, filename);140});141142// Update internal dir contents.143self.updateDirContents(absDirPath);144145// Since we've never seen this path before, recursively add child146// directories of this path. TODO: Don't do fs.readdirSync on the147// same dir twice in a row. We already do an fs.statSync in148// this.updateDirContents() and we're just going to do another one149// here...150fs.readdirSync(absDirPath).forEach(function(filename) {151var filepath = path.join(absDirPath, filename);152153// Look for directories.154if (fs.statSync(filepath).isDirectory()) {155self.add(filepath);156}157});158};159160DWp.updateDirContents = function(absDirPath, event, fsWatchReportedFilename) {161var self = this;162163if (!hasOwn.call(self.dirContents, absDirPath)) {164self.dirContents[absDirPath] = [];165}166167var oldContents = self.dirContents[absDirPath];168var newContents = fs.readdirSync(absDirPath);169170var deleted = {};171var added = {};172173oldContents.forEach(function(filename) {174deleted[filename] = true;175});176177newContents.forEach(function(filename) {178if (hasOwn.call(deleted, filename)) {179delete deleted[filename];180} else {181added[filename] = true;182}183});184185var deletedNames = Object.keys(deleted);186deletedNames.forEach(function(filename) {187self.emit(188"deleted",189path.relative(190self.rootPath,191path.join(absDirPath, filename)192)193);194});195196var addedNames = Object.keys(added);197addedNames.forEach(function(filename) {198self.emit(199"added",200path.relative(201self.rootPath,202path.join(absDirPath, filename)203)204);205});206207// So changed is not deleted or added?208if (fsWatchReportedFilename &&209!hasOwn.call(deleted, fsWatchReportedFilename) &&210!hasOwn.call(added, fsWatchReportedFilename))211{212self.emit(213"changed",214path.relative(215self.rootPath,216path.join(absDirPath, fsWatchReportedFilename)217)218);219}220221// If any of the things removed were directories, remove their watchers.222// If a dir was moved, hopefully two changed events fired?223// 1) event in dir where it was removed224// 2) event in dir where it was moved to (added)225deletedNames.forEach(function(filename) {226var filepath = path.join(absDirPath, filename);227delete self.dirContents[filepath];228delete self.watchers[filepath];229});230231// if any of the things added were directories, recursively deal with them232addedNames.forEach(function(filename) {233var filepath = path.join(absDirPath, filename);234if (fs.existsSync(filepath) &&235fs.statSync(filepath).isDirectory())236{237self.add(filepath);238// mighttttttt need a self.updateDirContents() here in case239// we're somehow adding a path that replaces another one...?240}241});242243// Update state of internal dir contents.244self.dirContents[absDirPath] = newContents;245};246247DWp.close = function() {248var watchers = this.watchers;249Object.keys(watchers).forEach(function(filename) {250watchers[filename].close();251});252};253254exports.Watcher = Watcher;255256257