diff --git a/README.md b/README.md index 61e72bc1..34b3b6df 100644 --- a/README.md +++ b/README.md @@ -388,6 +388,7 @@ const fs = new Filer.FileSystem(options, callback); * [fs.removexattr(path, name, callback)](#removexattr) * [fs.fremovexattr(fd, name, callback)](#fremovexattr) * [fs.watch(filename, [options], [listener])](#watch) +* [fs.watchFile(filename, [options], [listener])](#watchFile) #### fs.rename(oldPath, newPath, callback) @@ -1323,6 +1324,24 @@ var watcher = fs.watch('/data', { recursive: true }, function(event, filename) { fs.writeFile('/data/subdir/file', 'data'); ``` +### fs.watchFile(filename, [options], [listener]) + +Watch for changes on a file at `filename`. The callback `listener` will be called each time the file is accessed. + +The `options` argument only supports the change in interval between checks measured in milliseconds and does not support perstistence like node. + +The `listener` receives two arguments that are the current stat object and previous stat object that are instances of `fs.Stat`. Reference to `fs.Stat` can be found +here: [`fs.Stat`](https://nodejs.org/api/fs.html#fs_class_fs_stats) + +Example: +```javascript +fs.watchFile('/myfile.txt', (curr, prev) => { + console.log(`the current mtime is: ${curr.mtime}`); + console.log(`the previous mtime was: ${prev.mtime}`); +}); +``` + + ### FileSystemShell Many common file system shell operations are available by using a `FileSystemShell` object. diff --git a/package-lock.json b/package-lock.json index f699b11d..595abf43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "filer", - "version": "1.1.0", + "version": "1.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js index 824128b3..859e9ad0 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -206,6 +206,60 @@ function FileSystem(options, callback) { return watcher; }; + //Object that uses filenames as keys + const statWatchers = new Map(); + + this.watchFile = function (filename, options, listener) { + let prevStat, currStat; + + if (Path.isNull(filename)) { + throw new Error('Path must be a string without null bytes.'); + } + // Checks to see if there were options passed in and if not, the callback function will be set here + if (typeof options === 'function') { + listener = options; + options = {}; + } + // default 5007ms interval, persistent is not used this project + const interval = options.interval || 5007; + listener = listener || nop; + + let intervalValue = statWatchers.get(filename); + + if(intervalValue) { + return; + } + else { + fs.stat(filename, function (err, stats) { + var value = setInterval(function () { + prevStat = currStat; + + //Conditional check for first run to set initial state for prevStat + if(!prevStat) { + prevStat = stats; + } + + currStat = stats; + + if (err) { + clearInterval(value); + console.warn('[Filer Error] fs.watchFile encountered an error' + err.message); + } + if (JSON.stringify(prevStat) !== JSON.stringify(currStat)) { + listener(prevStat, currStat); + } + // Set a new prevStat based on previous + prevStat = currStat; + }, + interval + ); + + // Stores interval return values + statWatchers.set(filename, value); + }); + } + }; + // Deal with various approaches to node ID creation function wrappedGuidFn(context) { return function (callback) { diff --git a/tests/index.js b/tests/index.js index beb517f2..62432455 100644 --- a/tests/index.js +++ b/tests/index.js @@ -41,6 +41,7 @@ require('./spec/trailing-slashes.spec'); require('./spec/times.spec'); require('./spec/time-flags.spec'); require('./spec/fs.watch.spec'); +require('./spec/fs.watchFile.spec'); require('./spec/fs.unwatchFile.spec'); require('./spec/errors.spec'); require('./spec/fs.shell.spec'); diff --git a/tests/spec/fs.watchFile.spec.js b/tests/spec/fs.watchFile.spec.js new file mode 100644 index 00000000..2a59a2cd --- /dev/null +++ b/tests/spec/fs.watchFile.spec.js @@ -0,0 +1,69 @@ +'use strict'; + +var util = require('../lib/test-utils.js'); +var expect = require('chai').expect; + +describe('fs.watchFile', function() { + beforeEach(util.setup); + afterEach(util.cleanup); + + it('should be a function', function() { + const fs = util.fs(); + expect(typeof fs.watchFile).to.equal('function'); + }); + + it('should throw an error if a file path is not defined', function() { + const fs = util.fs(); + + const fn = () => fs.watchFile(undefined); + expect(fn).to.throw(); + }); + + it('should throw an error if a file is deleted', function() { + const fs = util.fs(); + + fs.writeFile('/myfile', 'data', function(error) { + if(error) throw error; + }); + + fs.watchFile('/myfile', function(prev, curr) { + expect(prev).to.exist; + expect(curr).to.exist; + }); + + fs.unlink('/myfile'); + + const fn = () => fs.watchFile('/myfile'); + expect(fn).to.throw(); + }); + + it('prev and curr should be equal if nothing has been changed in the file', function() { + const fs = util.fs(); + + fs.writeFile('/myfile', 'data', function(error) { + if(error) throw error; + }); + + fs.watchFile('/myfile', function(prev, curr) { + expect(prev).to.equal(curr); + }); + }); + + it('prev and curr should not be equal if something has been changed in the file', function() { + const fs = util.fs(); + + fs.writeFile('/myfile', 'data', function(error) { + if(error) throw error; + }); + + fs.watchFile('/myfile', function(prev, curr) { + expect(prev).to.equal(curr); + + fs.writeFile('/myfile', 'data2', function(error) { + if(error) throw error; + }); + + expect(prev).to.not.equal(curr); + }); + }); +}); \ No newline at end of file