"use strict";
|
|
|
|
var fs = require("fs");
|
|
var path = require("path");
|
|
|
|
function buildNumberCheck(field) {
|
|
return function(typ, options, val) {
|
|
var value = parseInt(val, 10);
|
|
|
|
if(value !== val || value <= 0 || typ !== "number") throw new Error("'" + field + "' option must be a positive integer number");
|
|
};
|
|
}
|
|
|
|
function buildStringCheck(field, check) {
|
|
return function(typ, options, val) {
|
|
if(typ !== "string") throw new Error("Don't know how to handle 'options." + field + "' type: " + typ);
|
|
|
|
options[field] = check(val);
|
|
};
|
|
}
|
|
|
|
function checkMeasure(v, what, units) {
|
|
var ret = {};
|
|
|
|
ret.num = parseInt(v, 10);
|
|
|
|
if(isNaN(ret.num)) throw new Error("Unknown 'options." + what + "' format: " + v);
|
|
|
|
if(ret.num <= 0) throw new Error("A positive integer number is expected for 'options." + what + "'");
|
|
|
|
ret.unit = v.replace(/^[ 0]*/g, "").substr((ret.num + "").length, 1);
|
|
|
|
if(ret.unit.length === 0) throw new Error("Missing unit for 'options." + what + "'");
|
|
|
|
if(! units[ret.unit]) throw new Error("Unknown 'options." + what + "' unit: " + ret.unit);
|
|
|
|
return ret;
|
|
}
|
|
|
|
var intervalUnits = {
|
|
M: true,
|
|
d: true,
|
|
h: true,
|
|
m: true,
|
|
s: true
|
|
};
|
|
|
|
function checkIntervalUnit(ret, unit, amount) {
|
|
if(parseInt(amount / ret.num, 10) * ret.num !== amount) throw new Error("An integer divider of " + amount + " is expected as " + unit + " for 'options.interval'");
|
|
}
|
|
|
|
function checkInterval(v) {
|
|
var ret = checkMeasure(v, "interval", intervalUnits);
|
|
|
|
switch(ret.unit) {
|
|
case "h":
|
|
checkIntervalUnit(ret, "hours", 24);
|
|
break;
|
|
|
|
case "m":
|
|
checkIntervalUnit(ret, "minutes", 60);
|
|
break;
|
|
|
|
case "s":
|
|
checkIntervalUnit(ret, "seconds", 60);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
var sizeUnits = {
|
|
B: true,
|
|
G: true,
|
|
K: true,
|
|
M: true
|
|
};
|
|
|
|
function checkSize(v) {
|
|
var ret = checkMeasure(v, "size", sizeUnits);
|
|
|
|
if(ret.unit === "K") return ret.num * 1024;
|
|
|
|
if(ret.unit === "M") return ret.num * 1048576;
|
|
|
|
if(ret.unit === "G") return ret.num * 1073741824;
|
|
|
|
return ret.num;
|
|
}
|
|
|
|
var checks = {
|
|
compress: function(typ, options, val) {
|
|
if(! val) throw new Error("A value for 'options.compress' must be specified");
|
|
|
|
if(typ === "boolean")
|
|
options.compress = function(src, dst) {
|
|
return "cat " + src + " | gzip -c9 > " + dst;
|
|
};
|
|
else if(typ === "string") {
|
|
//if(val != "bzip" && val != "gzip")
|
|
if(val !== "gzip") throw new Error("Don't know how to handle compression method: " + val);
|
|
}
|
|
else if(typ !== "function") throw new Error("Don't know how to handle 'options.compress' type: " + typ);
|
|
},
|
|
|
|
highWaterMark: function() {},
|
|
|
|
history: function(typ) {
|
|
if(typ !== "string") throw new Error("Don't know how to handle 'options.history' type: " + typ);
|
|
},
|
|
|
|
immutable: function() {},
|
|
|
|
initialRotation: function() {},
|
|
|
|
interval: buildStringCheck("interval", checkInterval),
|
|
|
|
maxFiles: buildNumberCheck("maxFiles"),
|
|
|
|
maxSize: buildStringCheck("maxSize", checkSize),
|
|
|
|
mode: function() {},
|
|
|
|
path: function(typ) {
|
|
if(typ !== "string") throw new Error("Don't know how to handle 'options.path' type: " + typ);
|
|
},
|
|
|
|
rotate: buildNumberCheck("rotate"),
|
|
|
|
rotationTime: function() {},
|
|
|
|
size: buildStringCheck("size", checkSize)
|
|
};
|
|
|
|
function checkOptions(options) {
|
|
if(! options) return {};
|
|
|
|
if(typeof options !== "object") throw new Error("Don't know how to handle 'options' type: " + typeof options);
|
|
|
|
var ret = {};
|
|
|
|
for(var opt in options) {
|
|
var val = options[opt];
|
|
var typ = typeof val;
|
|
|
|
if(! (opt in checks)) throw new Error("Unknown option: " + opt);
|
|
|
|
ret[opt] = options[opt];
|
|
checks[opt](typ, ret, val);
|
|
}
|
|
|
|
if(! ret.interval) {
|
|
delete ret.immutable;
|
|
delete ret.initialRotation;
|
|
delete ret.rotationTime;
|
|
}
|
|
|
|
if(ret.rotate) {
|
|
delete ret.history;
|
|
delete ret.immutable;
|
|
delete ret.maxFiles;
|
|
delete ret.maxSize;
|
|
delete ret.rotationTime;
|
|
}
|
|
|
|
if(ret.immutable) delete ret.compress;
|
|
|
|
if(ret.rotationTime) delete ret.initialRotation;
|
|
|
|
return ret;
|
|
}
|
|
|
|
function pad(num) {
|
|
return (num > 9 ? "" : "0") + num;
|
|
}
|
|
|
|
function createClassical(filename) {
|
|
return function(index) {
|
|
if(! index) return filename;
|
|
|
|
return filename + "." + index;
|
|
};
|
|
}
|
|
|
|
function createGenerator(filename) {
|
|
return function(time, index) {
|
|
if(! time) return filename;
|
|
|
|
var month = time.getFullYear() + "" + pad(time.getMonth() + 1);
|
|
var day = pad(time.getDate());
|
|
var hour = pad(time.getHours());
|
|
var minute = pad(time.getMinutes());
|
|
|
|
return month + day + "-" + hour + minute + "-" + pad(index) + "-" + filename;
|
|
};
|
|
}
|
|
|
|
function makePath(name, callback) {
|
|
var dir = path.parse(name).dir;
|
|
|
|
fs.mkdir(dir, function(e) {
|
|
if(e) {
|
|
if(e.code === "ENOENT") return makePath(dir, callback);
|
|
|
|
if(e.code !== "EEXIST") return callback(e);
|
|
}
|
|
|
|
callback();
|
|
});
|
|
}
|
|
|
|
function setEvents(self) {
|
|
self.once("error", function(err) {
|
|
self.err = err;
|
|
self.end();
|
|
});
|
|
|
|
self.once("finish", self._clear.bind(self));
|
|
|
|
self.on("rotated", function() {
|
|
self.rotation = null;
|
|
self._rewrite();
|
|
});
|
|
|
|
if((self.options.maxFiles || self.options.maxSize) && ! self.options.rotate) self.on(self.options.immutable ? "open" : "rotated", self.history.bind(self));
|
|
}
|
|
|
|
module.exports = {
|
|
checkOptions: checkOptions,
|
|
createClassical: createClassical,
|
|
createGenerator: createGenerator,
|
|
makePath: makePath,
|
|
setEvents: setEvents
|
|
};
|