Skip to content

Commit

Permalink
installLiveCssFileWatcherInServer: Watch the containing directory ins…
Browse files Browse the repository at this point in the history
…tead of the file when using fs.watch.

Hopefully this resolves some of the weirdness we've seen (see #23).
  • Loading branch information
papandreou committed Mar 17, 2012
1 parent 69c5a19 commit a19fadf
Showing 1 changed file with 101 additions and 69 deletions.
170 changes: 101 additions & 69 deletions lib/installLiveCssFileWatcherInServer.js
@@ -1,57 +1,108 @@
var fs = require('fs'),
path = require('path'),
URL = require('url'),
clientsByFileName = {},
mtimeByFileName = {};

function buildRelativeUrl(fromUrl, toUrl) {
var minLength = Math.min(fromUrl.length, toUrl.length),
commonPrefixLength = 0;
while (commonPrefixLength < minLength && fromUrl[commonPrefixLength] === toUrl[commonPrefixLength]) {
commonPrefixLength += 1;
}
var commonPrefix = fromUrl.substr(0, commonPrefixLength),
commonPrefixMatch = commonPrefix.match(/^(file:\/\/|[^:]+:\/\/[^\/]+\/)/);
URL = require('url');

if (!path.relative) {
// Add path.relative for node.js < 0.6 (taken from 0.6.13)
// path.relative(from, to)
path.relative = function(from, to) {
from = path.resolve(from).substr(1);
to = path.resolve(to).substr(1);

function trim(arr) {
var start = 0;
for (; start < arr.length; start++) {
if (arr[start] !== '') break;
}

var end = arr.length - 1;
for (; end >= 0; end--) {
if (arr[end] !== '') break;
}

if (commonPrefixMatch) {
var fromFragments = fromUrl.substr(commonPrefixMatch[1].length).replace(/^\/+/, "").replace(/[^\/]+$/, "").split(/\//),
toFragments = toUrl.substr(commonPrefixMatch[1].length).replace(/^\/+/, "").split(/\//);
if (start > end) return [];
return arr.slice(start, end - start + 1);
}

fromFragments.pop();
var fromParts = trim(from.split('/'));
var toParts = trim(to.split('/'));

var i = 0;
while (i < fromFragments.length && i < toFragments.length && fromFragments[i] === toFragments[i]) {
i += 1;
var length = Math.min(fromParts.length, toParts.length);
var samePartsLength = length;
for (var i = 0; i < length; i++) {
if (fromParts[i] !== toParts[i]) {
samePartsLength = i;
break;
}
}
var relativeUrl = toFragments.slice(i).join("/");
while (i < fromFragments.length) {
relativeUrl = '../' + relativeUrl;
i += 1;

var outputParts = [];
for (var i = samePartsLength; i < fromParts.length; i++) {
outputParts.push('..');
}
return relativeUrl;
} else {
return toUrl; // No dice
}

outputParts = outputParts.concat(toParts.slice(samePartsLength));

return outputParts.join('/');
};
}

function clientToString(client) {
return client.handshake.address.address + ':' + client.handshake.address.port;
}

module.exports = function (app, options, sio) {
var io = sio.listen(app);
var io = sio.listen(app),
clientsByFileName = {},
isWatchedByDirName = {},
mtimeByFileName = {};

function notifyWatchers(fileName) {
if (fileName in clientsByFileName) {
var rootRelativePath = '/' + path.relative(options.documentRoot, fileName);
if (options.debug && clientsByFileName[fileName].length) {
console.warn(' Notifying ' + clientsByFileName[fileName].length + ' watcher(s):\n ' +
clientsByFileName[fileName].map(clientToString).join('\n '));
}
clientsByFileName[fileName].forEach(function (client) {
client.emit('change', rootRelativePath);
});
}
}

function notifyWatchersIfMtimeIncreased(fileName, eventName) {
if (fileName in clientsByFileName) {
if (!(fileName in mtimeByFileName)) {
mtimeByFileName[fileName] = Infinity;
notifyWatchers(fileName);
} else {
fs.stat(fileName, function (err, stats) {
if (err) {
return;
}
if (stats.mtime > mtimeByFileName[fileName]) {
notifyWatchers(fileName);
} else if (options.debug) {
console.warn('Suppressing notification for ' + fileName + ', the mtime of the file is still the same');
}
mtimeByFileName[fileName] = stats.mtime;
});
}
}
}

if (!options.debug) {
io.set('log level', 1);
}
io.sockets.on('connection', function (client) {
if (options.debug) {
console.warn('Client ' + clientToString(client) + ' connected');
}
client.on('watch', function (assetUrls) {
client.on('watch', function (rootRelativePaths) {
if (options.debug) {
console.warn('Client subscribed to ' + assetUrls.length + ' file(s):');
console.warn('Client subscribed to ' + rootRelativePaths.length + ' file(s):');
}
assetUrls.forEach(function (rootRelativePath) {
rootRelativePaths.forEach(function (rootRelativePath) {
if (options.debug) {
console.warn(' ' + rootRelativePath);
}
Expand All @@ -73,50 +124,31 @@ module.exports = function (app, options, sio) {
} else {
clientsByFileName[fileName] = [client];

function notifyWatchers () {
if (options.debug) {
console.warn(fileName + ' changed on disc');
if (clientsByFileName[fileName].length) {
console.warn(' Notifying ' + clientsByFileName[fileName].length + ' watcher(s):\n ' +
clientsByFileName[fileName].map(clientToString).join('\n '));
}
}
clientsByFileName[fileName].forEach(function (client) {
client.emit('change', rootRelativePath);
});
}

if (options.debug) {
console.warn(' Starting to watch ' + fileName + ' using ' + ((fs.watch && !options.watchfile) ? 'fs.watch' : 'fs.watchFile'));
}
if (fs.watch && !options.watchfile) {
fs.watch(fileName, function handleWatchNotification(event) {
// Resubscribe if the file is renamed (Mac OSX):
if (event === 'rename') {
fs.watch(fileName, handleWatchNotification);
}
if (!(fileName in mtimeByFileName)) {
mtimeByFileName[fileName] = Infinity;
notifyWatchers();
} else {
fs.stat(fileName, function (err, stats) {
if (err) {
return;
}
if (stats.mtime > mtimeByFileName[fileName]) {
notifyWatchers();
} else if (options.debug) {
console.warn("Ignoring " + event + " from fs.watch, the mtime of the file is still the same");
}
mtimeByFileName[fileName] = stats.mtime;
});
var dirName = path.dirname(fileName);
if (!isWatchedByDirName[dirName]) {
if (options.debug) {
console.warn('Starting to ' + dirName + ' using fs.watch');
}
});
fs.watch(dirName, function (eventName, fileName) {
fileName = path.resolve(dirName, fileName); // Absolutify fileName
if (options.debug) {
console.warn('fs.watch: ' + eventName + ' ' + fileName);
}
notifyWatchersIfMtimeIncreased(fileName, eventName);
});
}
} else {
// TODO: Poll the upstream server if the file isn't found on disc (or no documentRoot is specified)
if (options.debug) {
console.warn('Starting to watch ' + fileName + ' using fs.watchFile');
}
fs.watchFile(fileName, function (currStat, prevStat) {
if (options.debug) {
console.warn('fs.watchFile: ' + fileName + ' changed on disc');
}
if (currStat.mtime.getTime() !== prevStat.mtime.getTime()) {
notifyWatchers();
notifyWatchers(fileName);
}
});
}
Expand Down

0 comments on commit a19fadf

Please sign in to comment.