1 var comb = require("comb");
  2 
  3 /**
  4  * @class In memory store for Node JS.
  5  *
  6  * @name Hive
  7  */
  8 exports = module.exports = comb.define(null, {
  9 
 10     instance : {
 11         /**
 12          * @lends Hive.prototype
 13          */
 14 
 15         __checkInterval : 1000,
 16 
 17         constructor : function(options) {
 18             options = options || {};
 19             //create a tree for times so we can quickly find values less
 20             //than a specified time
 21             this.__timeTree = new comb.collections.AVLTree({
 22                 compare : function(a, b) {
 23                     var ret = 0;
 24                     if (a.expires < b.expires) {
 25                         ret = -1;
 26                     } else if (a.expires > b.expires) {
 27                         ret = 1;
 28                     }
 29                     return ret;
 30                 }
 31             });
 32             //use a tree so we can have complex keys later
 33             //otherwise we are limited to just strings
 34             //also this performs great on look up!
 35             //Also allows quick lookups on multiple values
 36             var cmp = options.compare ? options.compare : function(a, b) {
 37                 var ret = 0;
 38                 if (a < b) {
 39                     ret = -1;
 40                 } else if (a > b) {
 41                     ret = 1;
 42                 }
 43                 return ret;
 44             }
 45             this.__values = new comb.collections.AVLTree({
 46                 compare : function(a, b) {
 47                     return cmp(a.key, b.key);
 48                 }
 49             });
 50             this.__cleanupInterval = setInterval(comb.hitch(this, this._checkValues), this.__checkInterval);
 51         },
 52 
 53         /**
 54          * Kills the clean up process for looking for expired keys.
 55          */
 56         kill : function() {
 57             clearInterval(this.__cleanupInterval);
 58         },
 59 
 60         /**
 61          * Casts a key
 62          * @param {*} key the key to cast
 63          */
 64         __castKey : function(key) {
 65             //TODO make this overridable.
 66             return "" + key;
 67         },
 68 
 69         /**
 70          * Checks for expired values.
 71          */
 72         _checkValues : function() {
 73             var timeTree = this.__timeTree,
 74                     valueTree = this.__values,
 75                     now = new Date().getTime();
 76             var vals = this.__timeTree.findLessThan(now);
 77             vals.forEach(function(v) {
 78                 timeTree.remove(v);
 79                 valueTree.remove(v);
 80             });
 81         },
 82 
 83         /**
 84          * Covert seconds to milliseconds
 85          * @param {Number} seconds number of seconds to convert.
 86          */
 87         _convertSeconds : function(seconds) {
 88             return seconds * 60000;
 89         },
 90 
 91         /**
 92          * Retrives all values with a key less than the provided key.
 93          *
 94          * @param {*} key the key to look up.
 95          *
 96          * @return {Array} an array of values.
 97          */
 98         getKeyLt : function(key) {
 99             key = this.__castKey(key);
100             return this.__values.findLessThan({key : key}, true).map(function(v) {
101                 return v.value
102             });
103         },
104 
105         /**
106          * Retrives all values with a key less or equal to a than the provided key.
107          *
108          * @param {*} key the key to look up.
109          *
110          * @return {Array} an array of values.
111          */
112         getKeyLte : function(key) {
113             key = this.__castKey(key);
114             return this.__values.findLessThan({key : key}).map(function(v) {
115                 return v.value
116             });
117         },
118 
119         /**
120          * Retrives all values with a greater than the provided key.
121          *
122          * @param {*} key the key to look up.
123          *
124          * @return {Array} an array of values.
125          */
126         getKeyGt : function(key) {
127             key = this.__castKey(key);
128             return this.__values.findGreaterThan({key : key}, true).map(function(v) {
129                 return v.value
130             });
131         },
132 
133         /**
134          * Retrives all values with a greater or equal to than the provided key.
135          *
136          * @param {*} key the key to look up.
137          *
138          * @return {Array} an array of values.
139          */
140         getKeyGte : function(key) {
141             key = this.__castKey(key);
142             return this.__values.findGreaterThan({key : key}).map(function(v) {
143                 return v.value
144             });
145         },
146 
147         /**
148          * Retrive the value for a specified key
149          *
150          * @param {*} key the key to look up.
151          *
152          * * @return {*} the value or null if not found.
153          */
154         get : function(key) {
155             key = this.__castKey(key);
156             var val = this.__values.find({key : key});
157             return val ? val.value : null;
158         },
159 
160         /**
161          * Set a key value pair.
162          * @param {*} key the key to store
163          * @param {*} value the value to asociate with the key.
164          * @param {Number} [expires=Infinity] if provided sets a max life on a key, value pair.
165          *
166          * * @return {*} the value
167          */
168         set : function(key, value, expires) {
169             expires = !expires ? Infinity : new Date().getTime() + this._convertSeconds(expires);
170             //we need to keep expires to ensure we can look up from tree also;
171             var node = {key : key, value : value, expires : expires};
172             //store the pointer in both
173             this.__values.insert(node);
174             if (expires != Infinity) {
175                 //dont worry about saving values that are set to infinity
176                 //as it doesnt matter
177                 this.__timeTree.insert(node);
178             }
179             return value;
180         },
181 
182         /**
183          * Replace a key value pair in this Store.
184          *
185          * @param {*} key the key to store
186          * @param {*} value the value to asociate with the key.
187          * @param {Number} [expires=Infinity] if provided sets a max life on a key, value pair.
188          *
189          * @return {*} the value
190          */
191         replace : function(key, value, expires) {
192             var currValue = this.__values.find({key : key});
193             if (currValue) {
194                 currValue.value = value;
195             } else {
196                 this.set(key, value, expires);
197             }
198             return value;
199         },
200 
201         /**
202          * Appends a value to a current value, if the current value is a string, and the appending
203          * value is a string then it will be appeneded to the value otherwise an array is created to
204          * store both values.
205          *
206          * @param {*} key the key to look up.
207          * @param {*} value the value to append
208          */
209         append : function(key, value) {
210             var currNode = this.__values.find({key : key});
211             if (currNode) {
212                 var currValue = currNode.value;
213                 //if they are both strings assume you can just appened it!
214                 if (comb.isString(currValue) && comb.isString(value)) {
215                     currNode.value += value;
216                 } else {
217                     //make it an array
218                     currNode.value = [currValue, value];
219                 }
220             }
221         },
222 
223         /**
224          * Prepends a value to a current value, if the current value is a string, and the prepedning
225          * value is a string then it will be prepeneded to the value otherwise an array is created to
226          * store both values.
227          *
228          * @param {*} key the key to look up.
229          * @param {*} value the value to prepend
230          */
231         prepend : function(key, value) {
232             var currNode = this.__values.find({key : key});
233             if (currNode) {
234                 var currValue = currNode.value;
235                 //if they are both strings assume you can just appened it!
236                 if (comb.isString(currValue) && comb.isString(value)) {
237                     currNode.value = value + currValue;
238                 } else {
239                     //make it an array
240                     currNode.value = [value, currValue];
241                 }
242             }
243         },
244 
245         /**
246          * Increments a value, if it is numeric.
247          *
248          * @param {*} key the key to look up the value to increment.
249          */
250         incr : function(key) {
251             var num;
252             var currNode = this.__values.find({key : key});
253             if (currNode) {
254                 var currValue = currNode.value;
255                 if (comb.isNumber(currValue)) {
256                     //update the pointers value
257                     return ++currNode.value;
258                 } else {
259                     num = Number(currValue);
260                     if (!isNaN(num)) {
261                         currNode.value = ++num;
262                     }
263                 }
264             }
265             return num;
266         },
267 
268         /**
269          * Decrements a value, if it is numeric.
270          *
271          * @param {*} key the key to look up the value to decrement.
272          */
273         decr : function(key) {
274             var num;
275             var currNode = this.__values.find({key : key});
276             if (currNode) {
277                 var currValue = currNode.value;
278                 if (comb.isNumber(currValue)) {
279                     //update the pointers value
280                     return --currNode.value;
281                 } else {
282                     num = Number(currValue);
283                     if (!isNaN(num)) {
284                         currNode.value = --num;
285                     }
286                 }
287             }
288             return num;
289         },
290 
291         /**
292          * Remove a value from this store.
293          *
294          * @param {*} key the key of the key value pair to remove.
295          */
296         remove : function(key) {
297             var currValue = this.__values.find({key : key});
298             if (currValue) {
299                 this.__values.remove(currValue);
300                 if (currValue.expires != Infinity) {
301                     this.__timeTree.remove(currValue);
302                 }
303             }
304             return null;
305         },
306 
307         /**
308          * Remove all values from the store.
309          */
310         flushAll : function() {
311             this.__values.clear();
312             this.__timeTree.clear();
313             return true;
314         }
315     }
316 
317 });
318