From 58a3a30a6b9221d146a23594e71442cce5c19fcd Mon Sep 17 00:00:00 2001 From: Andrew Koung Date: Tue, 19 Mar 2019 21:49:10 -0400 Subject: [PATCH 1/7] Issue #654 working on adding feature fs.watchFile() --- package-lock.json | 2 +- src/filesystem/interface.js | 37 ++++++++++++++++++++++++ tests/index.js | 1 + tests/spec/fs.watchFile.spec.js | 50 +++++++++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/spec/fs.watchFile.spec.js 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..840ff886 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -206,6 +206,43 @@ function FileSystem(options, callback) { return watcher; }; + //Object that uses filenames as keys + const statWatchers = new Map(); + + this.watchFile = function(filename, options, listener) { + const 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; + + //stores interval return values + statWatchers.set(filename, value); + + var value = setInterval(function() { + fs.stat(filename, function(err, stats) { + if(err) { + //console.log(err); + } + //record file curr + //compare curr-prev + //if theres a difference file change + }); + }, + interval + ); + + return watcher; + }; + // 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..90cb2fcc --- /dev/null +++ b/tests/spec/fs.watchFile.spec.js @@ -0,0 +1,50 @@ +'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() { + var fs = util.fs(); + expect(typeof fs.watchFile).to.equal('function'); + }); + + it('should get a change event when writing a file', function(done) { + const fs = util.fs(); + + const watcher = fs.watchFile('/myfile.txt', function(event, filename) { + expect(event).to.equal('change'); + expect(filename).to.equal('/myfile.txt'); + watcher.close(); + done(); + }); + + fs.writeFile('/myfile.txt', 'data', function(error) { + if(error) throw error; + }); + }); + + /* + it('should get a change event when renaming a file', function(done) { + const fs = util.fs(); + + fs.writeFile('/myfile.txt', 'data', function(error) { + if(error) throw error; + + const watcher = fs.watchFile('/myfile.txt', function(event, filename) { + expect(event).to.equal('change'); + expect(filename).to.equal('/myfile.txt'); + watcher.close(); + done(); + }); + + fs.rename('/myfile.txt', '/mynewfile.txt', function(error) { + if(error) throw error; + }); + }); + }); + */ +}); \ No newline at end of file From 0d292068c456442b86f6b825f49a4c98ad27df6d Mon Sep 17 00:00:00 2001 From: Andrew Koung Date: Tue, 19 Mar 2019 21:55:29 -0400 Subject: [PATCH 2/7] added more logic --- src/filesystem/interface.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js index 840ff886..558e08a9 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -224,17 +224,25 @@ function FileSystem(options, callback) { const interval = options.interval || 5007; listener = listener || nop; + //Stores prev value to compare + fs.stat(filename, function(err, stats) { prevStat = stats}); + //stores interval return values statWatchers.set(filename, value); var value = setInterval(function() { fs.stat(filename, function(err, stats) { if(err) { - //console.log(err); + console.log(err); + } + currStat = stats; + if((currStat.mtime - prevStat.mtime) == 0) { + //No changes + } + else { + //if theres a difference file change + //call listener here } - //record file curr - //compare curr-prev - //if theres a difference file change }); }, interval From 8a8e4219d2deb3f6815024951e315b5e09f37759 Mon Sep 17 00:00:00 2001 From: Andrew Koung Date: Wed, 20 Mar 2019 19:22:49 -0400 Subject: [PATCH 3/7] made changes --- src/filesystem/interface.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js index 558e08a9..69b43ca8 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -224,7 +224,7 @@ function FileSystem(options, callback) { const interval = options.interval || 5007; listener = listener || nop; - //Stores prev value to compare + //Stores initial prev value to compare fs.stat(filename, function(err, stats) { prevStat = stats}); //stores interval return values @@ -235,20 +235,17 @@ function FileSystem(options, callback) { if(err) { console.log(err); } + //Store the current stat currStat = stats; - if((currStat.mtime - prevStat.mtime) == 0) { - //No changes - } - else { - //if theres a difference file change - //call listener here + if(prevStat != currStat) { + listener(prevStat, currStat); } + //Set a new prevStat based on previous + prevStat = currStat; }); }, interval ); - - return watcher; }; // Deal with various approaches to node ID creation From 1c7c00142ef83a13820ddf879bcd67a79c2d192a Mon Sep 17 00:00:00 2001 From: Andrew Koung Date: Mon, 25 Mar 2019 00:05:19 -0400 Subject: [PATCH 4/7] partially made some requested changes, but other changes still need to be discussed --- src/filesystem/interface.js | 52 +++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js index 69b43ca8..de5b7f14 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -210,42 +210,44 @@ function FileSystem(options, callback) { const statWatchers = new Map(); this.watchFile = function(filename, options, listener) { - const prevStat, currStat; + 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 + // 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 + // default 5007ms interval, persistent is not used this project const interval = options.interval || 5007; listener = listener || nop; - //Stores initial prev value to compare - fs.stat(filename, function(err, stats) { prevStat = stats}); - - //stores interval return values - statWatchers.set(filename, value); - - var value = setInterval(function() { - fs.stat(filename, function(err, stats) { - if(err) { - console.log(err); - } - //Store the current stat - currStat = stats; - if(prevStat != currStat) { - listener(prevStat, currStat); - } - //Set a new prevStat based on previous - prevStat = currStat; - }); - }, - interval - ); + // Stores initial prev value to compare + fs.stat(filename, function(err, stats) { + prevStat = stats; + + // Stores interval return values + statWatchers.set(filename, value); + + var value = setInterval(function() { + fs.stat(filename, function(err, stats) { + if(err) { + console.log(err); + } + // Store the current stat + currStat = stats; + if(Object.toJSON(prevStat) !== Object.toJSON(currStat)) { + listener(prevStat, currStat); + } + // Set a new prevStat based on previous + prevStat = currStat; + }); + }, + interval + ); + }); }; // Deal with various approaches to node ID creation From 3fd3fb0c6d5d7c5115009e43ce09aebfe3ca8c1d Mon Sep 17 00:00:00 2001 From: Andrew Koung Date: Mon, 25 Mar 2019 00:07:15 -0400 Subject: [PATCH 5/7] removed outddated test methods --- tests/spec/fs.watchFile.spec.js | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/tests/spec/fs.watchFile.spec.js b/tests/spec/fs.watchFile.spec.js index 90cb2fcc..0620b7a2 100644 --- a/tests/spec/fs.watchFile.spec.js +++ b/tests/spec/fs.watchFile.spec.js @@ -12,10 +12,11 @@ describe('fs.watchFile', function() { expect(typeof fs.watchFile).to.equal('function'); }); + /* it('should get a change event when writing a file', function(done) { const fs = util.fs(); - const watcher = fs.watchFile('/myfile.txt', function(event, filename) { + fs.watchFile('/myfile.txt', function(event, filename) { expect(event).to.equal('change'); expect(filename).to.equal('/myfile.txt'); watcher.close(); @@ -26,25 +27,5 @@ describe('fs.watchFile', function() { if(error) throw error; }); }); - - /* - it('should get a change event when renaming a file', function(done) { - const fs = util.fs(); - - fs.writeFile('/myfile.txt', 'data', function(error) { - if(error) throw error; - - const watcher = fs.watchFile('/myfile.txt', function(event, filename) { - expect(event).to.equal('change'); - expect(filename).to.equal('/myfile.txt'); - watcher.close(); - done(); - }); - - fs.rename('/myfile.txt', '/mynewfile.txt', function(error) { - if(error) throw error; - }); - }); - }); */ }); \ No newline at end of file From 3f746b014c06e33396aa7a78ee18fabc094e70f7 Mon Sep 17 00:00:00 2001 From: Andrew Koung Date: Fri, 29 Mar 2019 00:53:45 -0400 Subject: [PATCH 6/7] rework logic and added some test scripts --- src/filesystem/interface.js | 57 +++++++++++++++++++-------------- tests/spec/fs.watchFile.spec.js | 25 ++++++++------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/filesystem/interface.js b/src/filesystem/interface.js index de5b7f14..1a999633 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -207,47 +207,56 @@ function FileSystem(options, callback) { }; //Object that uses filenames as keys - const statWatchers = new Map(); + const statWatchers = new Map(); - this.watchFile = function(filename, options, listener) { - let prevStat, currStat; + 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; + listener = options; options = {}; } // default 5007ms interval, persistent is not used this project const interval = options.interval || 5007; listener = listener || nop; - - // Stores initial prev value to compare - fs.stat(filename, function(err, stats) { - prevStat = stats; - - // Stores interval return values - statWatchers.set(filename, value); - - var value = setInterval(function() { - fs.stat(filename, function(err, stats) { - if(err) { - console.log(err); - } - // Store the current stat + + let intervalValue = statWatchers.get(filename); + + // Checks to see if there's a pre-existing watcher on the file + if (intervalValue === undefined) { + // Stores initial prev value to compare + 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 === undefined) { + prevStat = stats; + } + currStat = stats; - if(Object.toJSON(prevStat) !== Object.toJSON(currStat)) { + + if (err) { + clearInterval(value); + console.warn('[Filer Error] fs.watchFile encountered an error: ' + err); + } + if (JSON.stringify(prevStat) !== JSON.stringify(currStat)) { listener(prevStat, currStat); } // Set a new prevStat based on previous prevStat = currStat; - }); - }, - interval - ); - }); + }, + interval + ); + + // Stores interval return values + statWatchers.set(filename, value); + }); + } }; // Deal with various approaches to node ID creation diff --git a/tests/spec/fs.watchFile.spec.js b/tests/spec/fs.watchFile.spec.js index 0620b7a2..d54c55f6 100644 --- a/tests/spec/fs.watchFile.spec.js +++ b/tests/spec/fs.watchFile.spec.js @@ -8,24 +8,27 @@ describe('fs.watchFile', function() { afterEach(util.cleanup); it('should be a function', function() { - var fs = util.fs(); + const fs = util.fs(); expect(typeof fs.watchFile).to.equal('function'); }); - /* - it('should get a change event when writing a file', function(done) { + 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(); + }); - fs.watchFile('/myfile.txt', function(event, filename) { - expect(event).to.equal('change'); - expect(filename).to.equal('/myfile.txt'); - watcher.close(); - done(); - }); + it('prev and curr should be populated', function() { + const fs = util.fs(); - fs.writeFile('/myfile.txt', 'data', function(error) { + fs.writeFile('/myfile', 'data', function(error) { if(error) throw error; + }); + + fs.watchFile('/myfile', function(prev, curr) { + expect(prev).to.exist; + expect(curr).to.exist; }); }); - */ }); \ No newline at end of file From babe89c06981ad198200d02e55e0c9acc7c54c87 Mon Sep 17 00:00:00 2001 From: Andrew Koung Date: Fri, 29 Mar 2019 16:30:55 -0400 Subject: [PATCH 7/7] made syntax changes, updated readme, and added more tests --- README.md | 19 +++++++++++++++ src/filesystem/interface.js | 15 ++++++------ tests/spec/fs.watchFile.spec.js | 41 ++++++++++++++++++++++++++++++--- 3 files changed, 65 insertions(+), 10 deletions(-) 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/src/filesystem/interface.js b/src/filesystem/interface.js index 1a999633..859e9ad0 100644 --- a/src/filesystem/interface.js +++ b/src/filesystem/interface.js @@ -226,15 +226,16 @@ function FileSystem(options, callback) { let intervalValue = statWatchers.get(filename); - // Checks to see if there's a pre-existing watcher on the file - if (intervalValue === undefined) { - // Stores initial prev value to compare + 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 === undefined) { + if(!prevStat) { prevStat = stats; } @@ -242,21 +243,21 @@ function FileSystem(options, callback) { if (err) { clearInterval(value); - console.warn('[Filer Error] fs.watchFile encountered an error: ' + err); + 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 diff --git a/tests/spec/fs.watchFile.spec.js b/tests/spec/fs.watchFile.spec.js index d54c55f6..2a59a2cd 100644 --- a/tests/spec/fs.watchFile.spec.js +++ b/tests/spec/fs.watchFile.spec.js @@ -19,7 +19,25 @@ describe('fs.watchFile', function() { expect(fn).to.throw(); }); - it('prev and curr should be populated', function() { + 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) { @@ -27,8 +45,25 @@ describe('fs.watchFile', function() { }); fs.watchFile('/myfile', function(prev, curr) { - expect(prev).to.exist; - expect(curr).to.exist; + 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