actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
305 lines (272 loc) • 10.8 kB
JavaScript
;
var fs = require('fs');
var async = require('async');
module.exports = {
startPriority: 300,
loadPriority: 300,
initialize: function(api, next){
api.cache = {};
api.cache.redisPrefix = api.config.general.cachePrefix;
api.cache.lockPrefix = api.config.general.lockPrefix;
api.cache.lockDuration = api.config.general.lockDuration;
api.cache.lockName = api.id;
api.cache.lockRetry = 100;
api.cache.keys = function(callback){
api.redis.client.keys(api.cache.redisPrefix + '*', callback);
};
api.cache.locks = function(callback){
api.redis.client.keys(api.cache.lockPrefix + '*', callback);
};
api.cache.size = function(callback){
api.cache.keys(function(error, keys){
var length = 0;
if(keys){ length = keys.length; }
callback(error, length);
});
};
api.cache.clear = function(callback){
api.cache.keys(function(error, keys){
if(error && typeof callback === 'function'){ return callback(error); }
var jobs = [];
keys.forEach(function(key){
jobs.push(function(done){ api.redis.client.del(key, done); });
});
async.parallel(jobs, function(error){
if(typeof callback === 'function'){ return callback(error); }
});
});
};
api.cache.dumpWrite = function(file, callback){
var data = {};
api.cache.keys(function(error, keys){
if(error && typeof callback === 'function'){ return callback(error); }
var jobs = [];
keys.forEach(function(key){
jobs.push(function(done){
api.redis.client.get(key, function(error, content){
if(error){ return done(error); }
data[key] = content;
return done();
});
});
});
async.parallel(jobs, function(error){
if(error){
if(typeof callback === 'function'){ return callback(error); }
}else{
fs.writeFileSync(file, JSON.stringify(data));
if(typeof callback === 'function'){ return callback(null, keys.length); }
}
});
});
};
api.cache.dumpRead = function(file, callback){
api.cache.clear(function(error){
if(error){
if(typeof callback === 'function'){ return callback(error); }
}else{
var jobs = [];
try{
var data = JSON.parse(fs.readFileSync(file));
}catch(error){ return callback(error); }
Object.keys(data).forEach(function(key){
var content = data[key];
jobs.push(function(done){ api.cache.saveDumpedElement(key, content, done); });
});
async.series(jobs, function(error){
if(typeof callback === 'function'){ return callback(error, Object.keys(data).length); }
});
}
});
};
api.cache.saveDumpedElement = function(key, content, callback){
try{
var parsedContent = JSON.parse(content);
}catch(error){ return callback(error); }
api.redis.client.set(key, content, function(error){
if(error){ return callback(error); }
else if(parsedContent.expireTimestamp){
var expireTimeSeconds = Math.ceil((parsedContent.expireTimestamp - new Date().getTime()) / 1000);
api.redis.client.expire(key, expireTimeSeconds, function(){
return callback(error);
});
}else{
return callback();
}
});
};
api.cache.load = function(key, options, callback){
// optons: options.expireTimeMS, options.retry
if(typeof options === 'function'){
callback = options;
options = {};
}
api.redis.client.get(api.cache.redisPrefix + key, function(error, cacheObj){
if(error){ api.log(error, 'error'); }
try{ cacheObj = JSON.parse(cacheObj); }catch(e){}
if(!cacheObj){
if(typeof callback === 'function'){
return callback(new Error(api.i18n.localize('Object not found')), null, null, null, null);
}
}else if(cacheObj.expireTimestamp >= new Date().getTime() || cacheObj.expireTimestamp === null){
var lastReadAt = cacheObj.readAt;
var expireTimeSeconds;
cacheObj.readAt = new Date().getTime();
if(cacheObj.expireTimestamp){
if(options.expireTimeMS){
cacheObj.expireTimestamp = new Date().getTime() + options.expireTimeMS;
expireTimeSeconds = Math.ceil(options.expireTimeMS / 1000);
}else{
expireTimeSeconds = Math.floor((cacheObj.expireTimestamp - new Date().getTime()) / 1000);
}
}
api.cache.checkLock(key, options.retry, function(error, lockOk){
if(error || lockOk !== true){
if(typeof callback === 'function'){ return callback(new Error(api.i18n.localize('Object Locked'))); }
}else{
api.redis.client.set(api.cache.redisPrefix + key, JSON.stringify(cacheObj), function(error){
if(typeof callback === 'function' && typeof expireTimeSeconds !== 'number'){
return callback(error, cacheObj.value, cacheObj.expireTimestamp, cacheObj.createdAt, lastReadAt);
}else{
api.redis.client.expire(api.cache.redisPrefix + key, expireTimeSeconds, function(error){
if(typeof callback === 'function'){ return callback(error, cacheObj.value, cacheObj.expireTimestamp, cacheObj.createdAt, lastReadAt); }
});
}
});
}
});
}else{
if(typeof callback === 'function'){
return callback(new Error(api.i18n.localize('Object Expired')));
}
}
});
};
api.cache.destroy = function(key, callback){
api.cache.checkLock(key, null, function(error, lockOk){
if(error || lockOk !== true){
if(typeof callback === 'function'){ callback(new Error(api.i18n.localize('Object Locked'))); }
}else{
api.redis.client.del(api.cache.redisPrefix + key, function(error, count){
if(error){ api.log(error, 'error'); }
var resp = true;
if(count !== 1){ resp = false; }
if(typeof callback === 'function'){ callback(error, resp); }
});
}
});
};
api.cache.save = function(key, value, expireTimeMS, callback){
if(typeof expireTimeMS === 'function' && typeof callback === 'undefined'){
callback = expireTimeMS;
expireTimeMS = null;
}
var expireTimeSeconds = null;
var expireTimestamp = null;
if(expireTimeMS !== null){
expireTimeSeconds = Math.ceil(expireTimeMS / 1000);
expireTimestamp = new Date().getTime() + expireTimeMS;
}
var cacheObj = {
value: value,
expireTimestamp: expireTimestamp,
createdAt: new Date().getTime(),
readAt: null
};
api.cache.checkLock(key, null, function(error, lockOk){
if(error || lockOk !== true){
if(typeof callback === 'function'){ return callback(new Error(api.i18n.localize('Object Locked'))); }
}else{
api.redis.client.set(api.cache.redisPrefix + key, JSON.stringify(cacheObj), function(error){
if(!error && expireTimeSeconds){
api.redis.client.expire(api.cache.redisPrefix + key, expireTimeSeconds, function(error){
if(typeof callback === 'function'){ return callback(error, true); }
});
}else{
if(typeof callback === 'function'){ return callback(error, true); }
}
});
}
});
};
api.cache.push = function(key, item, callback){
var object = JSON.stringify({data: item});
api.redis.client.rpush(api.cache.redisPrefix + key, object, function(error){
if(typeof callback === 'function'){ callback(error); }
});
};
api.cache.pop = function(key, callback){
api.redis.client.lpop(api.cache.redisPrefix + key, function(error, object){
if(error){ return callback(error); }
if(!object){ return callback(); }
var item;
try{
item = JSON.parse(object);
}catch(error){ return callback(error); }
return callback(null, item.data);
});
};
api.cache.listLength = function(key, callback){
api.redis.client.llen(api.cache.redisPrefix + key, callback);
};
api.cache.lock = function(key, expireTimeMS, callback){
if(typeof expireTimeMS === 'function' && callback === null){
expireTimeMS = expireTimeMS;
expireTimeMS = null;
}
if(expireTimeMS === null){
expireTimeMS = api.cache.lockDuration;
}
api.cache.checkLock(key, null, function(error, lockOk){
if(error || lockOk !== true){
return callback(error, false);
}else{
api.redis.client.setnx(api.cache.lockPrefix + key, api.cache.lockName, function(error){
if(error){
return callback(error);
}else{
api.redis.client.expire(api.cache.lockPrefix + key, Math.ceil(expireTimeMS / 1000), function(error){
lockOk = true;
if(error){ lockOk = false; }
return callback(error, lockOk);
});
}
});
}
});
};
api.cache.unlock = function(key, callback){
api.cache.checkLock(key, null, function(error, lockOk){
if(error || lockOk !== true){
return callback(error, false);
}else{
api.redis.client.del(api.cache.lockPrefix + key, function(error){
lockOk = true;
if(error){ lockOk = false; }
return callback(error, lockOk);
});
}
});
};
api.cache.checkLock = function(key, retry, callback, startTime){
if(startTime === null){ startTime = new Date().getTime(); }
api.redis.client.get(api.cache.lockPrefix + key, function(error, lockedBy){
if(error){
return callback(error, false);
}else if(lockedBy === api.cache.lockName || lockedBy === null){
return callback(null, true);
}else{
var delta = new Date().getTime() - startTime;
if(retry === null || retry === false || delta > retry){
return callback(null, false);
}else{
return setTimeout(function(){
api.cache.checkLock(key, retry, callback, startTime);
}, api.cache.lockRetry);
}
}
});
};
next();
}
};