223 lines
6.6 KiB
JavaScript
223 lines
6.6 KiB
JavaScript
|
// Generated by CoffeeScript 2.4.1
|
||
|
var Tail, environment, events, fs,
|
||
|
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };
|
||
|
|
||
|
events = require("events");
|
||
|
|
||
|
fs = require('fs');
|
||
|
|
||
|
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));
|
||
|
}
|
||
|
return results;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
constructor(filename, options = {}) {
|
||
|
var err, fromBeginning;
|
||
|
super(filename, options);
|
||
|
this.readBlock = this.readBlock.bind(this);
|
||
|
this.change = this.change.bind(this);
|
||
|
this.filename = 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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
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) {
|
||
|
return setTimeout((() => {
|
||
|
return this.watch();
|
||
|
}), 1000);
|
||
|
} else {
|
||
|
if (this.logger) {
|
||
|
this.logger.error(`'rename' event for ${this.filename}. File not available.`);
|
||
|
}
|
||
|
return this.emit("error", `'rename' event for ${this.filename}. File not available.`);
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// @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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unwatch() {
|
||
|
if (this.watcher) {
|
||
|
this.watcher.close();
|
||
|
} else {
|
||
|
fs.unwatchFile(this.filename);
|
||
|
}
|
||
|
this.isWatching = false;
|
||
|
this.queue = [];
|
||
|
if (this.logger) {
|
||
|
return this.logger.info("Unwatch ", this.filename);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
exports.Tail = Tail;
|