webpack/lib/node/NodeWatchFileSystem.js

252 lines
6.2 KiB
JavaScript
Raw Normal View History

2013-01-31 01:49:25 +08:00
/*
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;
2013-02-01 01:00:22 +08:00
/**
*
* @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
2013-02-11 07:17:29 +08:00
* @param callback {function(err, filesModified: String[], dirsModified: String[], fileTimestamps: Object, dirTimestamps: Object)] called once after change plus delay
2013-02-01 01:00:22 +08:00
* @param callbackUndelayed {function()} called once after first change
*/
NodeWatchFileSystem.prototype.watch = function(files, dirs, startTime, delay, callback, callbackUndelayed) {
2013-02-01 15:03:38 +08:00
var inputFileSystem = this.inputFileSystem;
2013-02-01 01:00:22 +08:00
if(!callbackUndelayed) callbackUndelayed = function() {}
2013-01-31 01:49:25 +08:00
var closed = false;
2013-02-01 01:00:22 +08:00
var fileTimestamps = {};
var dirTimestamps = {};
2013-02-11 07:17:29 +08:00
var filesModified = {};
var dirsModified = {};
2013-02-01 01:00:22 +08:00
2013-11-26 23:19:06 +08:00
var lastChangeTime;
2013-02-11 07:17:29 +08:00
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];
});
2013-02-01 01:00:22 +08:00
items.sort(function(a,b) {
2014-06-25 00:53:32 +08:00
if(a.path === b.path) return 0;
2013-02-01 01:00:22 +08:00
return a.path < b.path ? -1 : 1;
});
2013-02-11 07:17:29 +08:00
items.forEach(function(item) {
if(item.files) {
item.files.sort();
}
});
2013-02-01 01:00:22 +08:00
var initialChange = false;
2013-02-11 07:17:29 +08:00
var change = function(path) {
2013-02-01 01:00:22 +08:00
initialChange = true;
}
function readStat(item, callback) {
2013-02-11 07:17:29 +08:00
if(item.context) {
2013-02-01 01:00:22 +08:00
fs.readdir(item.path, function(err, files) {
2013-02-11 07:17:29 +08:00
function onTimestamp(ts) {
if(!dirTimestamps[item.context] || dirTimestamps[item.context] < ts)
dirTimestamps[item.context] = ts;
2013-02-01 15:03:38 +08:00
if(ts >= startTime) {
2013-02-11 07:17:29 +08:00
dirsModified[item.context] = true;
change(item.path);
2013-02-01 01:00:22 +08:00
}
2013-02-11 07:17:29 +08:00
return callback();
2013-02-01 01:00:22 +08:00
}
2013-02-11 07:17:29 +08:00
if(err) return onTimestamp(Infinity);
2013-02-01 01:00:22 +08:00
async.map(files, function(file, callback) {
2013-02-11 07:17:29 +08:00
file = path.join(item.path, file);
var isFile = false;
if(item.files) {
if(binarySearch(item.files, function(path) {
2014-06-25 00:53:32 +08:00
if(path === file) return 0;
2013-02-11 07:17:29 +08:00
return path < file ? -1 : 1;
}) >= 0) {
isFile = true;
2013-02-01 01:00:22 +08:00
}
2013-02-11 07:17:29 +08:00
}
fs.stat(file, function(err, stat) {
var ts = err ? Infinity : stat.mtime.getTime();
if(isFile) {
fileTimestamps[file] = ts;
if(ts >= startTime) filesModified[file] = true;
2013-02-01 01:00:22 +08:00
}
2013-02-11 07:17:29 +08:00
return callback(null, ts);
2013-02-01 01:00:22 +08:00
});
}, function(err, timestamps) {
2013-02-11 07:17:29 +08:00
if(err) return onTimestamp(Infinity);
var ts = timestamps.reduce(function(max, ts) {
if(ts > max)
return ts;
return max;
}, 0);
onTimestamp(ts);
2013-02-01 01:00:22 +08:00
});
2013-02-11 07:17:29 +08:00
});
} 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);
2013-02-01 01:00:22 +08:00
}
}
2013-02-11 07:17:29 +08:00
async.forEach(items, function processItem(item, callback) {
2013-02-01 01:00:22 +08:00
var isRunning = false;
var isScheduled = false;
item.watcher = fs.watch(item.path, function() {
if(isRunning) return isScheduled = true;
isRunning = true;
readStat(item, done);
});
2013-02-11 07:17:29 +08:00
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();
});
}
2013-02-01 01:00:22 +08:00
function done() {
if(closed) return;
if(isScheduled) {
isScheduled = false;
readStat(item, done);
} else {
isRunning = false;
}
}
}, function() {
2013-02-11 07:17:29 +08:00
var timeout;
2013-02-01 01:00:22 +08:00
if(initialChange) {
callbackUndelayed();
2013-02-11 07:17:29 +08:00
if(delay) {
2013-11-26 23:19:06 +08:00
lastChangeTime = Date.now();
2013-02-11 07:17:29 +08:00
change = restartDelay;
timeout = setTimeout(onTimeout, delay);
} else onTimeout();
2013-02-01 01:00:22 +08:00
} else {
2013-02-11 07:17:29 +08:00
change = function(path) {
2013-02-01 01:00:22 +08:00
callbackUndelayed();
2013-02-11 07:17:29 +08:00
if(delay) {
2013-11-26 23:19:06 +08:00
lastChangeTime = Date.now();
2013-02-11 07:17:29 +08:00
change = restartDelay;
timeout = setTimeout(onTimeout, delay);
} else {
change = function() {};
onTimeout();
}
2013-02-01 01:00:22 +08:00
};
}
2013-02-11 07:17:29 +08:00
function restartDelay() {
2013-11-26 23:19:06 +08:00
lastChangeTime = Date.now();
2013-02-11 07:17:29 +08:00
clearTimeout(timeout);
timeout = setTimeout(onTimeout, delay);
}
2013-02-01 01:00:22 +08:00
});
// 7.
function onTimeout() {
2013-11-26 23:19:06 +08:00
var nextSecond = Math.ceil(lastChangeTime / 1000) * 1000;
var timeToNextSecond = nextSecond - Date.now();
if(timeToNextSecond > 0) {
setTimeout(onTimeout, timeToNextSecond);
return;
}
2013-02-11 07:17:29 +08:00
change = function() {};
2013-02-01 01:00:22 +08:00
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);
2013-02-11 07:17:29 +08:00
2013-02-01 01:00:22 +08:00
close();
}
2013-02-11 07:17:29 +08:00
2013-02-01 01:00:22 +08:00
function close() {
closed = true;
2013-02-11 07:17:29 +08:00
items.forEach(function closeItem(item) {
2013-02-01 01:00:22 +08:00
item.watcher.close();
2013-02-11 07:17:29 +08:00
if(item.children) item.children.forEach(closeItem);
});
2013-02-01 01:00:22 +08:00
}
2013-02-11 07:17:29 +08:00
2013-01-31 01:49:25 +08:00
return {
2013-02-01 15:03:38 +08:00
close: close
2013-01-31 01:49:25 +08:00
}
};
2013-02-01 01:00:22 +08:00
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]);
2014-06-25 00:53:32 +08:00
if(comp === 0) return middle;
2013-02-01 01:00:22 +08:00
if(comp > 0) right = middle-1;
if(comp < 0) left = middle+1;
}
return -1;
}