dl
Version:
DreamLab Libs
261 lines (218 loc) • 9.76 kB
JavaScript
var Class = require("core").Class;
var assertions = require("core").common.Assertions;
var Memcache = function () {
this.initialize = function (pool) {
this._lifetime = 10;
this._kChunkLength = 800 * 1024;
this._firstByteTimeout = 1000;
this._connectionPool = pool;
};
this.get = function (key, callback) {
assertions.isString(key, Memcache.Exception.WRONG_TYPE);
var that = this;
var counter = 0;
var buffers = [];
var dataLength = 0;
this._connectionPool.getSocket(function (socket) {
//TODO: jest mozliwosc ze connection pool nie da mi socketa na czas
var timeout = setTimeout(function () {
// oddaje socket do puli
//socket.destroy(); //nie mozna tak robic mowie jak kubek
that._connectionPool.givebackSocket(socket);
console.error('emerg', 'MEMCACHE timeout after 1000ms for key: ' + key);
callback(null);
}, that._firstByteTimeout);
var chunker = function (data) {
// dostalem pierwszy pakiet czyszcze timeout
clearTimeout(timeout);
// sprawdzam bo moze byc null gdy nie ma takiego klucza
if (data) {
buffers.push(data);
dataLength += data.length;
if (data.length == that._kChunkLength) {
// teraz musze pobrac kolejny chunk
//console.log('i need download another chunk');
counter += 1;
that._getChunk(socket, that._getKeyChunked(key, counter), chunker);
} else {
// to juz koniec bo chunk nie jest wielkosci max
that._connectionPool.givebackSocket(socket);
callback(that._mergeBufferArray(buffers, dataLength));
}
} else {
// jezeli nie ma danych to koniec
that._connectionPool.givebackSocket(socket);
callback(that._mergeBufferArray(buffers, dataLength));
}
};
// pobieram pierwszy chunk
that._getChunk(socket, that._getKeyChunked(key, counter), chunker);
});
};
this._mergeBufferArray = function (buffers, dataLength) {
if (dataLength == 0) {
return null;
}
var buffer = new Buffer(dataLength);
var bufferPosition = 0;
for (var i = 0, l = buffers.length; i < l; i++) {
buffers[i].copy(buffer, bufferPosition);
bufferPosition += buffers[i].length;
}
return buffer;
};
this._getChunk = function (socket, key, callback) {
// INFO: get chunk nigdy nie powinien zwracac socketa
// oddawac do connection poola powinien tylko 'get'
var dataLength = 0, buffers = [];
socket.on('data', function (e) {
//console.log('getChunk: ' + key);
// jezeli E(nd) lub C(ommand bad format)
if (e[0] == 69 || e[0] == 67) {
callback(null);
return;
}
dataLength += e.length;
buffers.push(e);
if (e.slice(e.length - 5).toString('ascii') === 'END\r\n') {
var _buffer = new Buffer(dataLength);
var bufferPosition = 0;
for (var i = 0, l = buffers.length; i < l; i++) {
buffers[i].copy(_buffer, bufferPosition);
bufferPosition += buffers[i].length;
}
var dataStart = 0;
for (var i = 0; i < _buffer.length; i++) {
if (_buffer[i] === 13 && _buffer[i+1] === 10) {
dataStart = i+2;
break;
}
}
callback(_buffer.slice(dataStart, _buffer.length-7));
}
});
var resultBuff = new Buffer('get ' + key +'\r\n', 'binary');
socket.write(resultBuff);
};
this._setChunk = function (socket, key, value, callback, lifeTime, waitForReply) {
var that = this;
var cmdBuffStart = new Buffer('set ' + key + ' 0 ' + lifeTime + ' ' + value.length + (!waitForReply ? ' noreply' : '') + '\r\n', 'binary');
var cmdBuffEnd = new Buffer('\r\n', 'binary');
var resultBuff = new Buffer(cmdBuffStart.length + value.length + cmdBuffEnd.length);
cmdBuffStart.copy(resultBuff);
value.copy(resultBuff, cmdBuffStart.length);
cmdBuffEnd.copy(resultBuff, (cmdBuffStart.length + value.length));
var dataLength = 0, buffers = [];
if (waitForReply) {
socket.on('data', function (e) {
// jezeli S(tored))
if (e[0]== 83 && e[1] == 84) {
callback(true);
return;
}
dataLength += e.length;
buffers.push(e);
if (e.slice(e.length - 2).toString('ascii') === '\r\n') {
//that._connectionPool.givebackSocket(socket);
var _buffer = new Buffer(dataLength), bufferPosition = 0;
for (var i = 0, l = buffers.length; i < l; i++) {
buffers[i].copy(_buffer, bufferPosition);
bufferPosition += buffers[i].length;
}
callback(false, _buffer.slice(0, _buffer.length-2));
}
});
}
socket.write(resultBuff, function () {
if (!waitForReply) {
callback(true);
}
});
};
this.set = function (key, value, callback, lifeTime, waitForReply) {
//TODO dac zeby bylo reply i wtedy oddawac socket
assertions.isString(key, Memcache.Exception.WRONG_TYPE);
var that = this;
if (lifeTime == 0) {
// cachujemy na zawsze
} else {
lifeTime = lifeTime || this._lifetime;
}
waitForReply = waitForReply || false;
var counter = 0;
var loopCounter = 0;
var chunkPos = null;
// pobieram wolny socket
this._connectionPool.getSocket(function (socket) {
// jezeli ktos nasetowal np string to zamieniam go na buffer
if (!Buffer.isBuffer(value)) {
value = new Buffer(value, 'utf8');
}
// licze w ilu chunkach bedzie zapisana wartosc
counter = Math.ceil(value.length / that._kChunkLength);
counter = counter > 0 ? counter : 1;
//console.log('COUNTER: ', counter);
// robie kopie dla loop'a aby nie podmienil wartosci
loopCounter = counter;
// iteruje tyle razy ile mam chunkow do zapisania
for (var i = 0; i < loopCounter; i++) {
chunkPos = that._getChunkPositions(i, value.length);
//console.log('CHUNK FROM: ' + chunkPos.start + " to " + chunkPos.stop);
that._setChunk(socket, that._getKeyChunked(key, i), value.slice(chunkPos.start, chunkPos.stop) , function (isChunkSuccess) {
counter--;
if (counter == 0) {
//console.log('Thats ALL! CALLING BACK...');
that._connectionPool.givebackSocket(socket);
if (callback) {
callback(true);
}
}
}, lifeTime, waitForReply);
}
});
};
this._getKeyChunked = function (key, chunkNumber) {
// pierwszy chunk jest identyczny z kluczem
// aby api memcache'a zostalo takie samo
if (chunkNumber == 0) {
return key;
} else {
return key + "_chunk_" + chunkNumber;
}
};
this._getChunkPositions = function (chunkIndex, length) {
var start = 0;
var stop = 0;
start = chunkIndex * this._kChunkLength;
stop = start + this._kChunkLength;
if (stop > length) {
stop = length;
}
return {start:start, stop:stop};
};
this['delete'] = function (key, callback) {
var that = this;
//TODO dac zeby bylo reply i wtedy oddawac socket
assertions.isString(key, Memcache.Exception.WRONG_TYPE);
this._connectionPool.getSocket(function (socket) {
var resultBuff = new Buffer('delete ' + key + ' noreply\r\n', 'binary');
socket.write(resultBuff, function () {
that._connectionPool.givebackSocket(socket);
});
});
};
this.setLifetime = function (lifetime) {
assertions.isNumber(lifetime, Memcache.Exception.WRONG_LIFETIME_TYPE);
this._lifetime = lifetime;
return this;
};
this.getLifetime = function () {
return this._lifetime;
};
};
Memcache = new Class(new Memcache());
Memcache.Exception = {};
Memcache.Exception.WRONG_KEY_TYPE = "Key has to be a string";
Memcache.Exception.WRONG_VALUE_TYPE = "Value has to be a string, number or object";
Memcache.Exception.WRONG_LIFETIME_TYPE = "Lifetime has to be a number";
exports.Memcache = Memcache;