Comparing version 2.0.4 to 2.1.0
381
lib/tail.js
@@ -1,230 +0,207 @@ | ||
// Generated by CoffeeScript 2.4.1 | ||
var Tail, environment, events, fs, path, | ||
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } }; | ||
let events = require(`events`) | ||
let fs = require('fs') | ||
let path = require('path') | ||
events = require("events"); | ||
// const environment = process.env['NODE_ENV'] || 'development' | ||
fs = require('fs'); | ||
class devNull { | ||
info() { }; | ||
error() { }; | ||
}; | ||
path = require('path'); | ||
class Tail extends events.EventEmitter { | ||
constructor(filename, options = {}) { | ||
super(); | ||
this.filename = filename; | ||
this.absPath = path.dirname(this.filename); | ||
this.separator = (options.separator !== undefined) ? options.separator : /[\r]{0,1}\n/;// null is a valid param | ||
this.fsWatchOptions = options.fsWatchOptions || {}; | ||
this.follow = options.follow || true; | ||
this.logger = options.logger || new devNull(); | ||
this.useWatchFile = options.useWatchFile || false; | ||
this.flushAtEOF = options.flushAtEOF || false; | ||
this.encoding = options.encoding || `utf-8`; | ||
const fromBeginning = options.fromBeginning || false; | ||
environment = process.env['NODE_ENV'] || 'development'; | ||
Tail = class Tail extends events.EventEmitter { | ||
readBlock() { | ||
var block, stream; | ||
boundMethodCheck(this, Tail); | ||
if (this.queue.length >= 1) { | ||
block = this.queue[0]; | ||
if (block.end > block.start) { | ||
stream = fs.createReadStream(this.filename, { | ||
start: block.start, | ||
end: block.end - 1, | ||
encoding: this.encoding | ||
}); | ||
stream.on('error', (error) => { | ||
if (this.logger) { | ||
this.logger.error(`Tail error: ${error}`); | ||
} | ||
return this.emit('error', error); | ||
}); | ||
stream.on('end', () => { | ||
var x; | ||
x = this.queue.shift(); | ||
if (this.queue.length > 0) { | ||
this.internalDispatcher.emit("next"); | ||
} | ||
if (this.flushAtEOF && this.buffer.length > 0) { | ||
this.emit("line", this.buffer); | ||
return this.buffer = ''; | ||
} | ||
}); | ||
return stream.on('data', (data) => { | ||
var chunk, i, len, parts, results; | ||
if (this.separator === null) { | ||
return this.emit("line", data); | ||
} else { | ||
this.buffer += data; | ||
parts = this.buffer.split(this.separator); | ||
this.buffer = parts.pop(); | ||
results = []; | ||
for (i = 0, len = parts.length; i < len; i++) { | ||
chunk = parts[i]; | ||
results.push(this.emit("line", chunk)); | ||
this.logger.info(`Tail starting...`) | ||
this.logger.info(`filename: ${this.filename}`); | ||
this.logger.info(`encoding: ${this.encoding}`); | ||
try { | ||
fs.accessSync(this.filename, fs.constants.F_OK); | ||
} catch (err) { | ||
if (err.code == 'ENOENT') { | ||
throw err | ||
} | ||
return results; | ||
} | ||
} | ||
this.buffer = ''; | ||
this.internalDispatcher = new events.EventEmitter(); | ||
this.queue = []; | ||
this.isWatching = false; | ||
// this.internalDispatcher.on('next',this.readBlock); | ||
this.internalDispatcher.on('next', () => { | ||
this.readBlock(); | ||
}); | ||
} | ||
this.logger.info(`fromBeginning: ${fromBeginning}`); | ||
let startingPos = undefined; | ||
if (fromBeginning) { | ||
startingPos = 0; | ||
//if fromBeginning triggers a check for content to flush the existing file | ||
//without waiting for a new appended line | ||
this.change(this.filename); | ||
} | ||
this.watch(startingPos); | ||
} | ||
} | ||
constructor(filename, options = {}) { | ||
var err, fromBeginning; | ||
super(filename, options); | ||
this.readBlock = this.readBlock.bind(this); | ||
this.change = this.change.bind(this); | ||
this.filename = filename; | ||
this.absPath = path.dirname(this.filename); | ||
({separator: this.separator = /[\r]{0,1}\n/, fsWatchOptions: this.fsWatchOptions = {}, follow: this.follow = true, logger: this.logger, useWatchFile: this.useWatchFile = false, flushAtEOF: this.flushAtEOF = false, encoding: this.encoding = "utf-8", fromBeginning = false} = options); | ||
if (this.logger) { | ||
this.logger.info("Tail starting..."); | ||
this.logger.info(`filename: ${this.filename}`); | ||
this.logger.info(`encoding: ${this.encoding}`); | ||
try { | ||
fs.accessSync(this.filename, fs.constants.F_OK); | ||
} catch (error1) { | ||
err = error1; | ||
if (err.code === 'ENOENT') { | ||
throw err; | ||
latestPosition() { | ||
try { | ||
return fs.statSync(this.filename).size; | ||
} catch (err) { | ||
this.logger.error(`size check for ${this.filename} failed: ${err}`); | ||
this.emit("error", `size check for ${this.filename} failed: ${err}`); | ||
throw err; | ||
} | ||
} | ||
} | ||
this.buffer = ''; | ||
this.internalDispatcher = new events.EventEmitter(); | ||
this.queue = []; | ||
this.isWatching = false; | ||
this.internalDispatcher.on('next', () => { | ||
return this.readBlock(); | ||
}); | ||
this.watch(fromBeginning); | ||
} | ||
change(filename) { | ||
var err, stats; | ||
boundMethodCheck(this, Tail); | ||
try { | ||
stats = fs.statSync(filename); | ||
} catch (error1) { | ||
err = error1; | ||
if (this.logger) { | ||
this.logger.error(`change event for ${filename} failed: ${err}`); | ||
} | ||
this.emit("error", `change event for ${filename} failed: ${err}`); | ||
return; | ||
readBlock() { | ||
if (this.queue.length >= 1) { | ||
const block = this.queue[0]; | ||
if (block.end > block.start) { | ||
let stream = fs.createReadStream(this.filename, { start: block.start, end: block.end - 1, encoding: this.encoding }); | ||
stream.on('error', (error) => { | ||
this.logger.error(`Tail error: ${error}`); | ||
this.emit('error', error); | ||
}); | ||
stream.on('end', () => { | ||
let _ = this.queue.shift(); | ||
if (this.queue.length > 0) { | ||
this.internalDispatcher.emit('next'); | ||
} | ||
if (this.flushAtEOF && this.buffer.length > 0) { | ||
this.emit('line', this.buffer); | ||
this.buffer = ""; | ||
} | ||
}); | ||
stream.on('data', (d) => { | ||
if (this.separator === null) { | ||
this.emit("line", d); | ||
} else { | ||
this.buffer += d; | ||
let parts = this.buffer.split(this.separator); | ||
this.buffer = parts.pop(); | ||
for (const chunk of parts) { | ||
this.emit("line", chunk); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
if (stats.size < this.pos) { //scenario where texts is not appended but it's actually a w+ | ||
this.pos = stats.size; | ||
} | ||
if (stats.size > this.pos) { | ||
this.queue.push({ | ||
start: this.pos, | ||
end: stats.size | ||
}); | ||
this.pos = stats.size; | ||
if (this.queue.length === 1) { | ||
return this.internalDispatcher.emit("next"); | ||
} | ||
} | ||
} | ||
watch(fromBeginning) { | ||
var err, stats; | ||
if (this.isWatching) { | ||
return; | ||
change(filename) { | ||
let p = this.latestPosition() | ||
if (p < this.pos) {//scenario where text is not appended but it's actually a w+ | ||
this.pos = p | ||
} else if (p > this.pos) { | ||
this.queue.push({ start: this.pos, end: p}); | ||
this.pos = p | ||
if (this.queue.length == 1) { | ||
this.internalDispatcher.emit("next"); | ||
} | ||
} | ||
} | ||
if (this.logger) { | ||
this.logger.info(`filesystem.watch present? ${fs.watch !== void 0}`); | ||
this.logger.info(`useWatchFile: ${this.useWatchFile}`); | ||
this.logger.info(`fromBeginning: ${fromBeginning}`); | ||
} | ||
this.isWatching = true; | ||
try { | ||
stats = fs.statSync(this.filename); | ||
} catch (error1) { | ||
err = error1; | ||
if (this.logger) { | ||
this.logger.error(`watch for ${this.filename} failed: ${err}`); | ||
} | ||
this.emit("error", `watch for ${this.filename} failed: ${err}`); | ||
return; | ||
} | ||
this.pos = fromBeginning ? 0 : stats.size; | ||
if (this.pos === 0) { | ||
this.change(this.filename); | ||
} | ||
if (!this.useWatchFile && fs.watch) { | ||
if (this.logger) { | ||
this.logger.info("watch strategy: watch"); | ||
} | ||
return this.watcher = fs.watch(this.filename, this.fsWatchOptions, (e, filename) => { | ||
return this.watchEvent(e, filename); | ||
}); | ||
} else { | ||
if (this.logger) { | ||
this.logger.info("watch strategy: watchFile"); | ||
} | ||
return fs.watchFile(this.filename, this.fsWatchOptions, (curr, prev) => { | ||
return this.watchFileEvent(curr, prev); | ||
}); | ||
} | ||
} | ||
rename(filename) { | ||
//MacOS sometimes throws a rename event for no reason. | ||
//Different platforms might behave differently. | ||
//see https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener | ||
//filename might not be present. | ||
//https://nodejs.org/api/fs.html#fs_filename_argument | ||
//Better solution would be check inode but it will require a timeout and | ||
// a sync file read. | ||
if (filename === void 0 || filename !== this.filename) { | ||
this.unwatch(); | ||
if (this.follow) { | ||
this.filename = path.join(this.absPath, filename); | ||
return this.rewatchId = setTimeout((() => { | ||
return this.watch(); | ||
}), 1000); | ||
} else { | ||
if (this.logger) { | ||
this.logger.error(`'rename' event for ${this.filename}. File not available.`); | ||
watch(startingPos) { | ||
if (this.isWatching) { | ||
return | ||
} | ||
return this.emit("error", `'rename' event for ${this.filename}. File not available.`); | ||
} | ||
} else { | ||
this.logger.info(`filesystem.watch present? ${fs.watch != undefined}`); | ||
this.logger.info(`useWatchFile: ${this.useWatchFile}`); | ||
this.isWatching = true; | ||
this.pos = (startingPos === undefined) ? this.latestPosition() : startingPos; | ||
try { | ||
if (!this.useWatchFile && fs.watch) { | ||
this.logger.info(`watch strategy: watch`); | ||
this.watcher = fs.watch(this.filename, this.fsWatchOptions, (e, filename) => { this.watchEvent(e, filename); }); | ||
} else { | ||
this.logger.info(`watch strategy: watchFile`); | ||
fs.watchFile(this.filename, this.fsWatchOptions, (curr, prev) => { this.watchFileEvent(curr, prev) }); | ||
} | ||
} catch (err) { | ||
this.logger.error(`watch for ${this.filename} failed: ${err}`); | ||
this.emit("error", `watch for ${this.filename} failed: ${err}`); | ||
return | ||
} | ||
} | ||
} | ||
// @logger.info("rename event but same filename") | ||
watchEvent(e, evtFilename) { | ||
if (e === 'change') { | ||
return this.change(this.filename); | ||
} else if (e === 'rename') { | ||
return this.rename(evtFilename); | ||
rename(filename) { | ||
//TODO | ||
//MacOS sometimes throws a rename event for no reason. | ||
//Different platforms might behave differently. | ||
//see https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener | ||
//filename might not be present. | ||
//https://nodejs.org/api/fs.html#fs_filename_argument | ||
//Better solution would be check inode but it will require a timeout and | ||
// a sync file read. | ||
if (filename === undefined || filename !== this.filename) { | ||
this.unwatch(); | ||
if (this.follow) { | ||
this.filename = path.join(this.absPath, filename); | ||
this.rewatchId = setTimeout((() => { | ||
this.watch(this.pos); | ||
}), 1000); | ||
} else { | ||
this.logger.error(`'rename' event for ${this.filename}. File not available anymore.`); | ||
this.emit("error", `'rename' event for ${this.filename}. File not available anymore.`); | ||
} | ||
} else { | ||
// this.logger.info("rename event but same filename") | ||
} | ||
} | ||
} | ||
watchFileEvent(curr, prev) { | ||
if (curr.size > prev.size) { | ||
this.pos = curr.size; // Update @pos so that a consumer can determine if entire file has been handled | ||
this.queue.push({ | ||
start: prev.size, | ||
end: curr.size | ||
}); | ||
if (this.queue.length === 1) { | ||
return this.internalDispatcher.emit("next"); | ||
} | ||
watchEvent(e, evtFilename) { | ||
if (e === 'change') { | ||
this.change(this.filename); | ||
} else if (e === 'rename') { | ||
this.rename(evtFilename); | ||
} | ||
} | ||
} | ||
unwatch() { | ||
if (this.watcher) { | ||
this.watcher.close(); | ||
} else { | ||
fs.unwatchFile(this.filename); | ||
watchFileEvent(curr, prev) { | ||
if (curr.size > prev.size) { | ||
this.pos = curr.size; //Update this.pos so that a consumer can determine if entire file has been handled | ||
this.queue.push({ start: prev.size, end: curr.size }); | ||
if (this.queue.length == 1) { | ||
this.internalDispatcher.emit("next"); | ||
} | ||
} | ||
} | ||
if (this.rewatchId) { | ||
clearTimeout(this.rewatchId); | ||
this.rewatchId = void 0; | ||
unwatch() { | ||
if (this.watcher) { | ||
this.watcher.close(); | ||
} else { | ||
fs.unwatchFile(this.filename); | ||
} | ||
if (this.rewatchId) { | ||
clearTimeout(this.rewatchId); | ||
this.rewatchId = undefined; | ||
} | ||
this.isWatching = false; | ||
this.queue = [];// TODO: is this correct behaviour? | ||
if (this.logger) { | ||
this.logger.info(`Unwatch ${this.filename}`); | ||
} | ||
} | ||
this.isWatching = false; | ||
this.queue = []; | ||
if (this.logger) { | ||
return this.logger.info("Unwatch ", this.filename); | ||
} | ||
} | ||
}; | ||
} | ||
exports.Tail = Tail; | ||
exports.Tail = Tail |
@@ -17,3 +17,3 @@ { | ||
], | ||
"version": "2.0.4", | ||
"version": "2.1.0", | ||
"homepage": "https://www.lucagrulla.com/node-tail", | ||
@@ -29,6 +29,7 @@ "repository": { | ||
"scripts": { | ||
"build": "cake build", | ||
"build": "rm -f ./lib/** && cp src/tail.js ./lib/", | ||
"prepare": "npm run build", | ||
"prepublishOnly": "npm run test", | ||
"test": "mocha" | ||
"test": "mocha", | ||
"coverage": "nyc npm run test" | ||
}, | ||
@@ -38,6 +39,6 @@ "license": "MIT", | ||
"devDependencies": { | ||
"coffeescript": "2.5.1", | ||
"chai": "4.x", | ||
"mocha": "7.x" | ||
"mocha": "8.x", | ||
"nyc": "^15.1.0" | ||
} | ||
} |
@@ -108,9 +108,8 @@ # Tail | ||
Tail is written in [CoffeeScript](https://coffeescript.org/). | ||
Tail is written in plain ES6.Pull Requests are welcome. | ||
The Cakefile generates the javascript that is then published to npm. | ||
## History | ||
Tail was born as part of a data firehose. Read about it [here](https://www.lucagrulla.com/posts/building-a-firehose-with-nodejs/). | ||
Tail original version was written in [CoffeeScript](https://coffeescript.org/). Since 2020 it's pure ES6. | ||
@@ -117,0 +116,0 @@ ## License |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
9916356
19
470
118
4
1