diff --git a/lib/file-ext.js b/lib/file-ext.js index 443d92a..08b1c86 100644 --- a/lib/file-ext.js +++ b/lib/file-ext.js @@ -3,6 +3,7 @@ var fs = require('fs') , ArrayExt = require('./array-ext') + , FileFollower = require('./file-follower') , LineEmitter = require('./line-emitter') , ObjectExt = require('./object-ext') , constants = require('constants') @@ -12,6 +13,7 @@ var fs = require('fs') FileExt = { eachLine: eachLine , exists: exists +, follow: follow , grep: grep , home: home , readLines: readLines @@ -58,14 +60,22 @@ function eachLine(f, optionsOrLineFn, endFn) { function exists(f) { try { - fs.statSync(f) - return true + fs.statSync(f); + return true; } catch (e) { - if (e.errno === constants.ENOENT) return false - throw e + if (e.errno === constants.ENOENT) return false; + throw e; } } +function follow(f, lineFn) { + var ff = new FileFollower(f); + ff.on('line', lineFn); + return { + stop: ff.stopFollowing.bind(ff) + }; +} + function grep(regex, f, callback) { if (!callback) throw new Error('grep requires a callback'); var results = []; diff --git a/lib/file-follower.js b/lib/file-follower.js new file mode 100644 index 0000000..e1d14fc --- /dev/null +++ b/lib/file-follower.js @@ -0,0 +1,74 @@ +// batteries +// Copyright 2010 - 2011 Sami Samhuri + +var fs = require('fs') + , util = require('util') + , EventEmitter = require('events').EventEmitter + , FileExt = require('./file-ext') + ; + +module.exports = FileFollower; + +// TODO: option to act like tail and only show the last N lines, N >= 0 +function FileFollower(file, options) { + options = options || {}; + var self = this; + this.file = file; + this.currSize = fs.statSync(file).size; + this.prevSize = this.currSize; + this.interval = options.interval || 1000; + FileExt.eachLine(file, + { line: function(line) { + self.emit('line', line); + } + , end: function() { + self.startFollowing(); + } + }); +} +util.inherits(FileFollower, EventEmitter); + +FileFollower.prototype.startFollowing = function() { + if (this._interval) { + console.warn('already following'); + return; + } + this.buffer = ''; + this.fd = fs.openSync(this.file, 'r'); + this._interval = setInterval(this.checkForLine.bind(this), this.interval); +}; + +FileFollower.prototype.stopFollowing = function() { + if (!this._interval) { + console.warn('not following'); + return; + } + delete this.buffer; + clearInterval(this._interval); + delete this._interval; + fs.closeSync(this.fd); + delete this.fd; +}; + +FileFollower.prototype.checkForLine = function() { + this.currSize = fs.statSync(this.file).size; + if (this.currSize > this.prevSize) { + var n = this.currSize - this.prevSize + , buf = new Buffer(n + 1) + , self = this + ; + fs.read(this.fd, buf, 0, n, this.prevSize, function(err, bytesRead, buffer) { + if (err) { + self.emit('error', err); + return; + } + self.buffer += buf.slice(0, bytesRead); + self.prevSize += bytesRead; + var i; + while ((i = self.buffer.indexOf('\n')) !== -1) { + self.emit('line', self.buffer.slice(0, i)); + self.buffer = self.buffer.slice(i + 1); + } + }); + } +};