Skip to content

Instantly share code, notes, and snippets.

@jan-swiecki
Last active March 4, 2016 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jan-swiecki/5358631 to your computer and use it in GitHub Desktop.
Save jan-swiecki/5358631 to your computer and use it in GitHub Desktop.
NodeJS `fs.watch` wrapper that checks file SHA1 and prevents multiple callback calls.
/**
* fs.watch wrapper that checks file SHA1
* and prevents multiple callback calls.
*
* Related: http://stackoverflow.com/q/12978924/1637178
*
* Usage is the same as fs.watch
*
* var onFileChange = require("./onFileChange.js");
*
* // same as fs.watch
* onFileChange("myAwesomeFile.txt", function()
* {
* console.log("Awesome file change!");
* });
*
* @author Jan Święcki <jan.swiecki@gmail.com>
*/
module.exports = (function()
{
var crypto = require("crypto");
var fs = require("fs");
var hashSums = {};
var fileWatchers = {};
var getSha1 = function(string)
{
return crypto.createHash("sha1").update(string).digest("hex");
}
var getSha1FromFile = function(path)
{
if(fs.existsSync(path))
{
return getSha1(fs.readFileSync(path));
}
else
{
// if file is moved/deleted then do nothing
return hashSums[path];
}
}
// actual onFileChange function
return function(path, callback)
{
function hasDifferentHash()
{
var h = getSha1FromFile(path);
if(h !== hashSums[path])
{
hashSums[path] = h;
return true;
}
else
{
return false;
}
}
function init()
{
if(! fs.existsSync(path))
{
console.error("Path "+path+" does not exists");
return false;
}
else
{
hashSums[path] || (hashSums[path] = getSha1FromFile(path));
// Try to prevent many events to fire at the same time.
// When someone is making many almost simultaneous file saves
// then SHA1 is sometimes not calculated properly. It happens
// probably because halfly saved file is being read. In that
// case the only thing we could try to do is to create file lock
// in NodeJS that is included by Operating System while saving file.
// Later I will try to experiment with fs-ext flock
// (https://github.com/baudehlo/node-fs-ext).
var execute = true;
fileWatchers[path] = fs.watch(path, function(event)
{
if(execute === true && event === 'change' && hasDifferentHash())
{
execute = false;
fileWatchers[path].close();
callback(path);
init();
}
});
}
}
if(typeof callback === 'function')
{
if(init() === false)
{
throw new Error("Cannot initialize");
}
return {
"close": fileWatchers[path].close
};
}
else
{
throw new Error("No callback for onFileChange");
}
}
}());
@jan-swiecki
Copy link
Author

Explanation of why we have multiple fired events:

You may notice in certain situations that a single creation event generates multiple Created events that are handled by your component. For example, if you use a FileSystemWatcher component to monitor the creation of new files in a directory, and then test it by using Notepad to create a file, you may see two Created events generated even though only a single file was created. This is because Notepad performs multiple file system actions during the writing process. Notepad writes to the disk in batches that create the content of the file and then the file attributes. Other applications may perform in the same manner. Because FileSystemWatcher monitors the operating system activities, all events that these applications fire will be picked up.

Source

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment