"use strict"; var compress = require("./compress"); var fs = require("fs"); var interval = require("./interval"); var path = require("path"); var util = require("util"); var utils = require("./utils"); var Writable = require("stream").Writable; function RotatingFileStream(filename, options) { if(! (this instanceof RotatingFileStream)) return new RotatingFileStream(filename, options); options = utils.checkOptions(options); if(typeof filename === "function") this.generator = filename; else if(typeof filename === "string") if(options.rotate) this.generator = utils.createClassical(filename); else this.generator = utils.createGenerator(filename); else throw new Error("Don't know how to handle 'filename' type: " + typeof filename); if(options.path) { var generator = this.generator; this.generator = function(time, index) { return path.join(options.path, generator(time, index)); }; } var opt = {}; if(options.highWaterMark) opt.highWaterMark = options.highWaterMark; if(options.mode) opt.mode = options.mode; Writable.call(this, opt); this.chunks = []; this.options = options; this.size = 0; this.write = this.write; // https://github.com/iccicci/rotating-file-stream/issues/19 utils.setEvents(this); process.nextTick(this.firstOpen.bind(this)); } util.inherits(RotatingFileStream, Writable); RotatingFileStream.prototype._close = function(done) { if(this.stream) { this.stream.on("finish", done); this.stream.end(); this.stream = null; } else done(); }; RotatingFileStream.prototype._rewrite = function() { const self = this; const callback = function() { if(self.ending) self._close(Writable.prototype.end.bind(self)); }; if(this.err) { const chunks = this.chunks; this.chunks = []; chunks.map(e => { if(e.cb) e.cb(); }); return callback(); } if(this.writing || this.rotation) return; if(this.options.size && this.size >= this.options.size) return this.rotate(); if(! this.stream) return; if(! this.chunks.length) return callback(); const chunk = this.chunks[0]; this.chunks.shift(); this.size += chunk.chunk.length; this.writing = true; this.stream.write(chunk.chunk, function(err) { self.writing = false; if(err) self.emit("error", err); if(chunk.cb) chunk.cb(); process.nextTick(self._rewrite.bind(self)); }); }; RotatingFileStream.prototype._write = function(chunk, encoding, callback) { this.chunks.push({ chunk: chunk, cb: callback }); this._rewrite(); }; RotatingFileStream.prototype._writev = function(chunks, callback) { chunks[chunks.length - 1].cb = callback; this.chunks = this.chunks.concat(chunks); this._rewrite(); }; RotatingFileStream.prototype.end = function() { var args = []; for(var i = 0; i < arguments.length; ++i) { if("function" === typeof arguments[i]) { this.once("finish", arguments[i]); break; } if(i > 1) break; args.push(arguments[i]); } this.ending = true; if(args.length) this.write.apply(this, args); else this._rewrite(); }; RotatingFileStream.prototype.firstOpen = function() { var self = this; if(this.options.immutable) return this.immutate(true); try { this.name = this.generator(null); } catch(e) { return this.emit("error", e); } this.once("open", this.interval.bind(this)); fs.stat(this.name, function(err, stats) { if(err) { if(err.code === "ENOENT") return self.open(); return self.emit("error", err); } if(! stats.isFile()) return self.emit("error", new Error("Can't write on: " + self.name + " (it is not a file)")); if(self.options.initialRotation) { var prev; self._interval(self.now()); prev = self.prev; self._interval(stats.mtime.getTime()); if(prev !== self.prev) return self.rotate(); } self.size = stats.size; if(! self.options.size || stats.size < self.options.size) return self.open(); if(self.options.interval) self._interval(self.now()); self.rotate(); }); }; RotatingFileStream.prototype.immutate = function(first, index, now) { if(! index) { index = 1; now = new Date(this.now()); } if(index >= 1001) return this.emit("error", this.exhausted()); try { this.name = this.generator(now, index); } catch(e) { return this.emit("error", e); } var open = function(size) { this.size = size; this.open(); this.once( "open", function() { if(! first) this.emit("rotated", this.last); this.last = this.name; this.interval(); }.bind(this) ); }.bind(this); fs.stat( this.name, function(err, stats) { if(err) { if(err.code === "ENOENT") return open(0); return this.emit("error", err); } if(! stats.isFile()) return this.emit("error", new Error("Can't write on: " + this.name + " (it is not a file)")); if(this.options.size && stats.size >= this.options.size) return this.immutate(first, index + 1, now); open(stats.size); }.bind(this) ); }; RotatingFileStream.prototype.move = function(retry) { var name; var self = this; var callback = function(err) { if(err) return self.emit("error", err); self.open(); if(self.options.compress) self.compress(name); else { self.emit("rotated", name); self.interval(); } }; this.findName({}, self.options.compress, function(err, found) { if(err) return callback(err); name = found; fs.rename(self.name, name, function(err) { if(err && err.code !== "ENOENT" && ! retry) return callback(err); if(! err) return callback(); utils.makePath(name, function(err) { if(err) return callback(err); self.move(true); }); }); }); }; RotatingFileStream.prototype.now = function() { return Date.now(); }; RotatingFileStream.prototype.open = function(retry) { var fd; var self = this; var options = { flags: "a" }; var callback = function(err) { if(err) self.emit("error", err); process.nextTick(self._rewrite.bind(self)); }; if("mode" in this.options) options.mode = this.options.mode; var stream = fs.createWriteStream(this.name, options); stream.once("open", function() { self.stream = stream; self.emit("open", self.name); callback(); }); stream.once("error", function(err) { if(err.code !== "ENOENT" && ! retry) return callback(err); utils.makePath(self.name, function(err) { if(err) return callback(err); self.open(true); }); }); }; RotatingFileStream.prototype.rotate = function() { this.size = 0; this.rotation = new Date(); this.emit("rotation"); this._clear(); this._close(this.options.rotate ? this.classical.bind(this, this.options.rotate) : this.options.immutable ? this.immutate.bind(this) : this.move.bind(this)); }; for(var i in compress) RotatingFileStream.prototype[i] = compress[i]; for(i in interval) RotatingFileStream.prototype[i] = interval[i]; module.exports = RotatingFileStream; module.exports.default = RotatingFileStream;