241 lines
5.9 KiB
JavaScript
241 lines
5.9 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
var fs = require("fs");
|
|
var path = require("path");
|
|
var async = require("async");
|
|
|
|
function NodeWatchFileSystem(inputFileSystem) {
|
|
this.inputFileSystem = inputFileSystem;
|
|
}
|
|
module.exports = NodeWatchFileSystem;
|
|
|
|
/**
|
|
*
|
|
* @param files {String[]} a sorted array of paths to files
|
|
* @param dirs {String[]} a sorted array of paths to directories
|
|
* @param startTime {number} the virtual start time
|
|
* @param delay {number} in ms, the time to wait to signal after the first change
|
|
* @param callback {function(err, filesModified: String[], dirsModified: String[], fileTimestamps: Object, dirTimestamps: Object)] called once after change plus delay
|
|
* @param callbackUndelayed {function()} called once after first change
|
|
*/
|
|
NodeWatchFileSystem.prototype.watch = function(files, dirs, startTime, delay, callback, callbackUndelayed) {
|
|
var inputFileSystem = this.inputFileSystem;
|
|
|
|
if(!callbackUndelayed) callbackUndelayed = function() {}
|
|
var closed = false;
|
|
var fileTimestamps = {};
|
|
var dirTimestamps = {};
|
|
var filesModified = {};
|
|
var dirsModified = {};
|
|
|
|
startTime = Math.floor(startTime / 1000) * 1000; // only 1 second accuracy
|
|
|
|
var directories = {};
|
|
dirs.forEach(function(dir) {
|
|
directories[dir] = {
|
|
context: dir,
|
|
files: []
|
|
};
|
|
});
|
|
files.forEach(function(file) {
|
|
var dir = path.dirname(file);
|
|
if(!directories[dir]) directories[dir] = {
|
|
files: []
|
|
};
|
|
directories[dir].files.push(file);
|
|
});
|
|
|
|
var items = Object.keys(directories).map(function(dir) {
|
|
directories[dir].path = dir;
|
|
return directories[dir];
|
|
});
|
|
items.sort(function(a,b) {
|
|
if(a.path == b.path) return 0;
|
|
return a.path < b.path ? -1 : 1;
|
|
});
|
|
items.forEach(function(item) {
|
|
if(item.files) {
|
|
item.files.sort();
|
|
}
|
|
});
|
|
|
|
var initialChange = false;
|
|
var change = function(path) {
|
|
initialChange = true;
|
|
}
|
|
|
|
function readStat(item, callback) {
|
|
if(item.context) {
|
|
fs.readdir(item.path, function(err, files) {
|
|
function onTimestamp(ts) {
|
|
if(!dirTimestamps[item.context] || dirTimestamps[item.context] < ts)
|
|
dirTimestamps[item.context] = ts;
|
|
if(ts >= startTime) {
|
|
dirsModified[item.context] = true;
|
|
change(item.path);
|
|
}
|
|
return callback();
|
|
}
|
|
if(err) return onTimestamp(Infinity);
|
|
async.map(files, function(file, callback) {
|
|
file = path.join(item.path, file);
|
|
var isFile = false;
|
|
if(item.files) {
|
|
if(binarySearch(item.files, function(path) {
|
|
if(path == file) return 0;
|
|
return path < file ? -1 : 1;
|
|
}) >= 0) {
|
|
isFile = true;
|
|
}
|
|
}
|
|
fs.stat(file, function(err, stat) {
|
|
var ts = err ? Infinity : stat.mtime.getTime();
|
|
if(isFile) {
|
|
fileTimestamps[file] = ts;
|
|
if(ts >= startTime) filesModified[file] = true;
|
|
}
|
|
return callback(null, ts);
|
|
});
|
|
}, function(err, timestamps) {
|
|
if(err) return onTimestamp(Infinity);
|
|
var ts = timestamps.reduce(function(max, ts) {
|
|
if(ts > max)
|
|
return ts;
|
|
return max;
|
|
}, 0);
|
|
onTimestamp(ts);
|
|
});
|
|
});
|
|
} else {
|
|
async.forEach(item.files, function(file, callback) {
|
|
fs.stat(file, function(err, stat) {
|
|
var ts = err ? Infinity : stat.mtime.getTime();
|
|
fileTimestamps[file] = ts;
|
|
if(ts >= startTime) {
|
|
filesModified[file] = true;
|
|
change(file);
|
|
}
|
|
return callback(null, ts);
|
|
});
|
|
}, callback);
|
|
}
|
|
}
|
|
async.forEach(items, function processItem(item, callback) {
|
|
var isRunning = false;
|
|
var isScheduled = false;
|
|
item.watcher = fs.watch(item.path, function() {
|
|
if(isRunning) return isScheduled = true;
|
|
isRunning = true;
|
|
readStat(item, done);
|
|
});
|
|
if(item.context) {
|
|
item.children = [];
|
|
fs.readdir(item.path, function(err, files) {
|
|
if(err) return change(file), onWatcherApplied();
|
|
async.forEach(files, function(file, callback) {
|
|
file = path.join(item.path, file);
|
|
fs.stat(file, function(err, stat) {
|
|
if(err) return change(file), callback();
|
|
if(!stat.isDirectory()) return callback();
|
|
var subitem = {
|
|
path: file,
|
|
context: item.context
|
|
};
|
|
item.children.push(subitem);
|
|
processItem(subitem, callback);
|
|
});
|
|
}, onWatcherApplied);
|
|
});
|
|
} else onWatcherApplied();
|
|
function onWatcherApplied() {
|
|
readStat(item, function() {
|
|
callback();
|
|
done();
|
|
});
|
|
}
|
|
function done() {
|
|
if(closed) return;
|
|
if(isScheduled) {
|
|
isScheduled = false;
|
|
readStat(item, done);
|
|
} else {
|
|
isRunning = false;
|
|
}
|
|
}
|
|
}, function() {
|
|
var timeout;
|
|
if(initialChange) {
|
|
|
|
callbackUndelayed();
|
|
if(delay) {
|
|
change = restartDelay;
|
|
timeout = setTimeout(onTimeout, delay);
|
|
} else onTimeout();
|
|
|
|
} else {
|
|
|
|
change = function(path) {
|
|
|
|
callbackUndelayed();
|
|
if(delay) {
|
|
change = restartDelay;
|
|
timeout = setTimeout(onTimeout, delay);
|
|
} else {
|
|
change = function() {};
|
|
onTimeout();
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
function restartDelay() {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(onTimeout, delay);
|
|
}
|
|
|
|
});
|
|
|
|
// 7.
|
|
function onTimeout() {
|
|
change = function() {};
|
|
if(closed) return;
|
|
var outdatedFiles = Object.keys(filesModified).sort();
|
|
var outdatedDirs = Object.keys(dirsModified).sort();
|
|
if(inputFileSystem && inputFileSystem.purge) {
|
|
inputFileSystem.purge(outdatedFiles);
|
|
inputFileSystem.purge(outdatedDirs);
|
|
}
|
|
callback(null, outdatedFiles, outdatedDirs, fileTimestamps, dirTimestamps);
|
|
|
|
close();
|
|
}
|
|
|
|
function close() {
|
|
closed = true;
|
|
items.forEach(function closeItem(item) {
|
|
item.watcher.close();
|
|
if(item.children) item.children.forEach(closeItem);
|
|
});
|
|
}
|
|
|
|
return {
|
|
close: close
|
|
}
|
|
};
|
|
|
|
function binarySearch(array, comparator) {
|
|
var left = 0;
|
|
var right = array.length - 1;
|
|
|
|
while(left <= right) {
|
|
var middle = ((left + right)/2)|0;
|
|
var comp = comparator(array[middle]);
|
|
if(comp == 0) return middle;
|
|
if(comp > 0) right = middle-1;
|
|
if(comp < 0) left = middle+1;
|
|
}
|
|
return -1;
|
|
}
|