caprese
Version:
Capped Log for Node.js
688 lines (637 loc) • 21.4 kB
JavaScript
// Generated by CoffeeScript 1.7.1
(function() {
var Caprese, DB_MAX_SIZE, DB_MIN_SIZE, DEFAULT_SIZE, ENTRY_DATE_LENGTH, ENTRY_HEADER_LENGTH, ENTRY_LENGTH_LENGTH, ENTRY_TYPE_LENGTH, EventEmitter, HEADER_SIZE, MAX_MESSAGE_SIZE, NOOP, Query, fs,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
fs = require('fs');
Query = require('./query');
EventEmitter = require('events').EventEmitter;
HEADER_SIZE = 12;
ENTRY_TYPE_LENGTH = 1;
ENTRY_DATE_LENGTH = 4;
ENTRY_LENGTH_LENGTH = 2;
ENTRY_HEADER_LENGTH = ENTRY_TYPE_LENGTH + ENTRY_DATE_LENGTH + ENTRY_LENGTH_LENGTH;
DB_MIN_SIZE = ENTRY_HEADER_LENGTH;
DB_MAX_SIZE = 4294967295;
MAX_MESSAGE_SIZE = 65535;
NOOP = function() {};
DEFAULT_SIZE = 1024 * 1024;
Caprese = (function(_super) {
__extends(Caprese, _super);
Caprese.prototype.fd = null;
Caprese.prototype.db = null;
Caprese.prototype.size = 0;
Caprese.prototype.index = null;
Caprese.prototype.first = null;
Caprese.prototype.lock = true;
Caprese.prototype.queued = null;
Caprese.prototype._closeOnDone = false;
Caprese.INFO = 0x01;
Caprese.ERROR = 0x03;
Caprese.WARNING = 0x02;
/*
new Caprese() - create 1MB resident capped log
new Caprese('./file.cap') - create 1MB capped log
new Caprese('./file.cap', {size: 1024}) - create 1KB capped log
new Caprese('./file.cap', {size: 1024}, function() {}) - create 1KB capped log and call a callback function
new Caprese({size: 1024}) - create 1KB resident capped log
new Caprese({size: 1024}, function() {}) - create 1KB resident capped log and call a callback function
new Caprese(function() {}) - create 1MB resident capped log and call a callback function
new Caprese('./file.cap', {size: 1024, resident: true}) - create 1KB resident capped log
*/
function Caprese() {
var callback;
Caprese.__super__.constructor.call(this);
this.index = [];
this.queued = [];
if (arguments.length) {
if (typeof arguments[0] === 'string') {
this.file = arguments[0];
if (arguments.length > 1) {
if (typeof arguments[1] === 'object') {
this.options = arguments[1];
if (arguments.length > 2) {
if (typeof arguments[2] === 'function') {
callback = arguments[2];
if (arguments.length > 3) {
throw new Error("Invalid arguments.");
}
} else {
throw new Error("Invalid arguments.");
}
} else {
callback = NOOP;
}
} else if (typeof arguments[1] === 'function') {
this.options = {
size: DEFAULT_SIZE
};
callback = arguments[1];
} else {
throw new Error("Invalid arguments.");
}
} else {
this.options = {
size: DEFAULT_SIZE
};
callback = NOOP;
}
} else if (typeof arguments[0] === 'object') {
this.options = arguments[0];
this.options.resident = true;
if (arguments.length > 1) {
if (typeof arguments[1] === 'function') {
callback = arguments[1];
if (arguments.length > 2) {
throw new Error("Invalid arguments.");
}
} else {
throw new Error("Invalid arguments.");
}
} else {
callback = NOOP;
}
} else if (typeof arguments[0] === 'function') {
callback = arguments[0];
this.options = {
size: DEFAULT_SIZE,
resident: true
};
if (arguments.length > 1) {
throw new Error("Invalid arguments.");
}
} else {
throw new Error("Invalid arguments.");
}
} else {
this.options = {
size: DEFAULT_SIZE,
resident: true
};
callback = NOOP;
}
if (arguments.length === 0) {
this.options = {
resident: true,
size: 1024 * 1024
};
callback = NOOP;
} else if (arguments.length === 1) {
if (arguments[0] instanceof Function) {
this.options = {
resident: true,
size: 1024 * 1024
};
callback = arguments[0];
} else if (typeof arguments[0] === 'string') {
this.file = arguments[0];
this.options = {
resident: true,
size: 1024 * 1024
};
callback = NOOP;
}
}
if (this.options instanceof Function) {
callback = this.options;
this.options = DEFAULT_OPTIONS;
}
if (this.options == null) {
this.options = DEFAULT_OPTIONS;
}
if (callback == null) {
callback = NOOP;
}
if (this.options.resident) {
this.create(null, callback);
} else {
if (this.options.overwrite) {
this.create(this.file, callback);
} else {
fs.exists(this.file, (function(_this) {
return function(exist) {
if (exist) {
return fs.open(_this.file, 'r+', function(err, fd) {
_this.fd = fd;
if (err) {
return callback(err);
}
return _this.load(callback);
});
} else {
return _this.create(_this.file, callback);
}
};
})(this));
}
}
}
Caprese.prototype._diff = function(from, to) {
if (to > from) {
return to - from;
} else if (to < from) {
return to + (this.size - from);
} else {
return 0;
}
};
Caprese.prototype._move = function(offset, length) {
var next;
next = offset + length;
if (length > 0) {
if (next >= this.size) {
next = next - this.size;
}
} else {
if (next < 0) {
next = this.size + next;
}
}
return next;
};
Caprese.prototype.add = function(type, date, message, callback) {
var buffer, length;
if (callback == null) {
callback = NOOP;
}
if (arguments.length === 2) {
message = date;
date = new Date();
}
if (arguments.length === 3) {
if (message instanceof Function) {
callback = message;
message = date;
date = new Date();
}
}
if (this.lock) {
return this.queue(type, date, message, callback);
}
if (typeof message !== 'string') {
message = String(message);
}
length = Buffer.byteLength(message, 'utf8');
if (length + ENTRY_HEADER_LENGTH > this.size) {
return callback(new Error("Message (" + length + " bytes) is larger than db limit of " + (this.size - ENTRY_HEADER_LENGTH) + " bytes"));
}
if (length > MAX_MESSAGE_SIZE) {
return callback(new Error("Message (" + length + " bytes) is larger than entry limit of " + MAX_MESSAGE_SIZE + " bytes"));
}
this.lock = true;
buffer = new Buffer(length + ENTRY_HEADER_LENGTH);
buffer.writeUInt8(type, 0);
buffer.writeUInt32LE(Math.floor(date.getTime() / 1000), 1);
buffer.writeUInt16LE(length, 5);
if (length) {
buffer.write(message, 7, length, 'utf8');
}
return this.clear(buffer.length, (function(_this) {
return function(err) {
if (err) {
_this.lock = false;
return callback(err);
}
return _this.write(buffer, _this.cursor, function(err, nextbyte) {
if (err) {
_this.lock = false;
return callback(err);
}
buffer = new Buffer(4);
buffer.writeUInt32LE(nextbyte, 0);
return _this._write(buffer, 0, 4, _this.size + 4, function(err) {
if (err) {
_this.lock = false;
return callback(err);
}
_this.index.push({
offset: _this._move(_this.cursor, ENTRY_HEADER_LENGTH),
length: length,
type: type,
date: date
});
_this.cursor = nextbyte;
_this.lock = false;
_this.unqueue();
_this.emit('add', type, date, message);
return typeof callback === "function" ? callback(null) : void 0;
});
});
};
})(this));
};
Caprese.prototype.clear = function(length, callback) {
var buffer, edit, first;
if (!this.index.length) {
return callback(null);
}
edit = false;
while (this.index.length && this._diff(this.cursor, this._move(this.index[0].offset, -ENTRY_HEADER_LENGTH)) < length) {
edit = true;
this.index.shift();
}
if (!edit) {
return callback(null);
}
if (this.index.length) {
first = this._move(this.index[0].offset, -ENTRY_HEADER_LENGTH);
} else {
first = this.cursor;
}
buffer = new Buffer(4);
buffer.writeUInt32LE(first, 0);
return this._write(buffer, 0, 4, this.size + 8, (function(_this) {
return function(err) {
if (err) {
return callback(null);
}
_this.first = first;
return callback(null);
};
})(this));
};
Caprese.prototype.close = function(callback) {
if (callback == null) {
callback = NOOP;
}
if (this.lock) {
this._closeOnDone = callback != null ? callback : true;
} else {
this.first = 0;
this.cursor = 0;
this.index = [];
this.queued = [];
this.lock = true;
if (this.fd) {
fs.close(this.fd, callback);
return this.fd = null;
} else if (this.db) {
this.db = null;
return setImmediate((function(_this) {
return function() {
return callback(null);
};
})(this));
}
}
};
Caprese.prototype.count = function() {
return this.index.length;
};
Caprese.prototype._create = function(size, callback) {
var buffer;
buffer = new Buffer([0x00]);
return this._write(buffer, 0, 1, 0, (function(_this) {
return function(err) {
if (err) {
return callback(err);
}
buffer = new Buffer(HEADER_SIZE);
buffer.fill(0x00);
buffer.write('cap', 0, 3, 'ascii');
buffer.writeUInt8(2, 3);
buffer.writeUInt32LE(0, 4);
buffer.writeUInt32LE(0, 4);
return _this._write(buffer, 0, HEADER_SIZE, size, function(err) {
if (err) {
return callback(err);
}
return _this.load(callback);
});
};
})(this));
};
Caprese.prototype.create = function(file, callback) {
var size;
size = this.options.size - HEADER_SIZE;
if (size < DB_MIN_SIZE) {
return callback(new Error("Minimum size is " + (DB_MIN_SIZE + HEADER_SIZE)));
}
if (size > DB_MAX_SIZE) {
return callback(new Error("Maximum size is " + (DB_MAX_SIZE + HEADER_SIZE)));
}
if (this.options.resident) {
return setImmediate((function(_this) {
return function() {
var ex;
try {
_this.db = new Buffer(_this.options.size);
return _this._create(size, callback);
} catch (_error) {
ex = _error;
return callback(ex);
}
};
})(this));
} else {
return fs.open(file, 'w+', (function(_this) {
return function(err, fd) {
_this.fd = fd;
if (err) {
return callback(err);
}
return _this._create(size, callback);
};
})(this));
}
};
Caprese.prototype.load = function(callback) {
if (this.fd) {
return fs.fstat(this.fd, (function(_this) {
return function(err, stats) {
var header;
if (err) {
return callback(err);
}
_this.size = stats.size - HEADER_SIZE;
if (_this.size < DB_MIN_SIZE) {
return callback(new Error("Invalid cap file (SIZE:" + stats.size + ")"));
}
header = new Buffer(HEADER_SIZE);
return _this._read(header, 0, HEADER_SIZE, _this.size, function(err) {
if (err) {
return callback(err);
}
if (!(header[0] === 0x63 && header[1] === 0x61 && header[2] === 0x70)) {
return callback(new Error("Invalid cap file (HEADER)"));
}
if (header[3] !== 0x02) {
return callback(new Error("Invalid cap file (VERSION)"));
}
_this.cursor = header.readUInt32LE(4);
if (_this.cursor >= _this.size) {
return callback(new Error("Invalid cap file (CURSOR)"));
}
_this.first = header.readUInt32LE(8);
if (_this.first >= _this.size) {
return callback(new Error("Invalid cap file (FIRST)"));
}
return _this.reindex(function(err) {
if (err) {
return callback(err);
}
_this.lock = false;
_this.unqueue();
return callback(null);
});
});
};
})(this));
} else if (this.db) {
return setImmediate((function(_this) {
return function() {
_this.size = _this.db.length - HEADER_SIZE;
_this.cursor = 0;
_this.first = 0;
_this.lock = false;
_this.unqueue();
return callback(null);
};
})(this));
} else {
return setImmediate(function() {
return callback(new Error("Failed to load database"));
});
}
};
Caprese.prototype.queue = function(type, date, message, callback) {
return this.queued.push({
type: type,
date: date,
message: message,
callback: callback
});
};
Caprese.prototype.unqueue = function() {
var msg;
if (this.queued.length) {
msg = this.queued.shift();
return this.add(msg.type, msg.date, msg.message, msg.callback);
} else {
if (this._closeOnDone) {
this.close(this._closeOnDone);
}
}
};
Caprese.prototype.select = function() {
return new Query(this);
};
Caprese.prototype.stats = function() {
console.log("Size:", this.size);
console.log("First:", this.first);
console.log("Cursor:", this.cursor);
console.log("Index:", this.index);
if (this.fd) {
return console.log("Buffer:", new Buffer(fs.readFileSync(this.file, 'binary')));
} else if (this.db) {
return console.log("Buffer:", this.db);
}
};
Caprese.prototype._read = function(buffer, offset, length, position, callback) {
if (this.fd) {
return fs.read(this.fd, buffer, offset, length, position, callback);
} else if (this.db) {
return setImmediate((function(_this) {
return function() {
var ex;
try {
_this.db.copy(buffer, offset, position, position + length);
return callback(null);
} catch (_error) {
ex = _error;
return callback(ex);
}
};
})(this));
} else {
return setImmediate(function() {
return callback(new Error("No database is initialized."));
});
}
};
Caprese.prototype.read = function(offset, length, callback) {
var buffer, next, part;
if (length > this.size) {
return callback(new RangeError("Reading more data than size of capped file"));
}
if (offset < 0 || offset >= this.size) {
return callback(new Error("Out of bounds"));
}
next = this._move(offset, length);
buffer = new Buffer(length);
if (offset + length > this.size) {
part = this.size - offset;
return this._read(buffer, 0, part, offset, (function(_this) {
return function(err) {
if (err) {
return callback(err);
}
return _this._read(buffer, part, length - part, 0, function(err) {
return callback(err, buffer, next);
});
};
})(this));
} else {
return this._read(buffer, 0, length, offset, (function(_this) {
return function(err) {
return callback(err, buffer, next);
};
})(this));
}
};
Caprese.prototype.reindex = function(callback) {
var buffer, cb, cursor, eof, fn, test;
buffer = new Buffer(1);
cursor = this.first;
eof = false;
test = function() {
return eof;
};
fn = (function(_this) {
return function(next) {
return _this.read(cursor, 1, function(err, buffer, nextbyte) {
var type;
if (err) {
return next(err);
}
if (buffer[0] === 0x00) {
eof = true;
return next(null);
} else if (buffer[0] === Caprese.INFO || buffer[0] === Caprese.ERROR || buffer[0] === Caprese.WARNING) {
type = buffer.readUInt8(0);
cursor = nextbyte;
return _this.read(cursor, 4, function(err, buffer, nextbyte) {
var date;
date = buffer.readUInt32LE(0);
cursor = nextbyte;
return _this.read(cursor, 2, function(err, buffer, nextbyte) {
var length;
if (err) {
return next(err);
}
length = buffer.readUInt16LE(0);
_this.index.push({
offset: nextbyte,
length: length,
type: type,
date: new Date(date * 1000)
});
cursor = _this._move(nextbyte, length);
if (cursor === _this.cursor) {
eof = true;
}
return next(null);
});
});
} else {
return next(new Error("Unknown type '" + buffer[0] + "' on offset '" + cursor + "'"));
}
});
};
})(this);
cb = function(err) {
if (err) {
return callback(err);
}
if (!eof) {
return fn(cb);
}
return callback(null);
};
return fn(cb);
};
Caprese.prototype._write = function(buffer, offset, length, position, callback) {
if (this.fd) {
return fs.write(this.fd, buffer, offset, length, position, callback);
} else if (this.db) {
return setImmediate((function(_this) {
return function() {
var ex;
try {
buffer.copy(_this.db, position, offset, offset + length);
return callback(null);
} catch (_error) {
ex = _error;
return callback(ex);
}
};
})(this));
} else {
return setImmediate(function() {
return callback(new Error("No database is initialized."));
});
}
};
Caprese.prototype.write = function(buffer, offset, callback) {
var next, part;
if (buffer.length > this.size) {
return callback(new RangeError("Writing more data than size of capped file"));
}
if (offset < 0 || offset >= this.size) {
return callback(new Error("Out of bounds"));
}
next = this._move(offset, buffer.length);
if (offset + buffer.length > this.size) {
part = this.size - offset;
return this._write(buffer, 0, part, offset, (function(_this) {
return function(err) {
if (err) {
return callback(err);
}
return _this._write(buffer, part, buffer.length - part, 0, function(err) {
return callback(err, next);
});
};
})(this));
} else {
return this._write(buffer, 0, buffer.length, offset, (function(_this) {
return function(err) {
return callback(err, next);
};
})(this));
}
};
return Caprese;
})(EventEmitter);
module.exports = Caprese;
}).call(this);