dcache 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +23 -0
- data/.yardopts +12 -0
- data/History.rdoc +3 -0
- data/Manifest.txt +13 -0
- data/README.rdoc +109 -0
- data/Rakefile +77 -0
- data/ext/dcache_ary/dcache_ary.c +276 -0
- data/ext/dcache_ary/extconf.rb +4 -0
- data/ext/dcache_list/dcache_list.c +460 -0
- data/ext/dcache_list/extconf.rb +4 -0
- data/lib/dcache.rb +243 -0
- data/spec/dcache_spec.rb +171 -0
- data/yard/reader.rb +33 -0
- metadata +110 -0
@@ -0,0 +1,460 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <time.h>
|
3
|
+
|
4
|
+
static ID CMP;
|
5
|
+
|
6
|
+
typedef struct ItemStruct Item;
|
7
|
+
|
8
|
+
/**
|
9
|
+
* An item in the linked list
|
10
|
+
*/
|
11
|
+
struct ItemStruct
|
12
|
+
{
|
13
|
+
VALUE key; /*< This item's key */
|
14
|
+
VALUE value; /*< Item's value */
|
15
|
+
time_t time; /*< Time when this item will time out. */
|
16
|
+
Item * prev; /*< Previous item in the linked list, or NULL if it's the head */
|
17
|
+
Item * next; /*< Next item in the linked list, or NULL if it's the last */
|
18
|
+
};
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Basic structure used by DCacheList
|
22
|
+
*/
|
23
|
+
typedef struct
|
24
|
+
{
|
25
|
+
Item * head; /*< Head of the linked list */
|
26
|
+
Item * end; /*< End of the linked list */
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Statistical variables
|
30
|
+
*/
|
31
|
+
unsigned long long int hits, misses, stored, removed;
|
32
|
+
size_t length; /*< Number of stored items / linked list's length */
|
33
|
+
size_t max_items; /*< Maximum number of items that can be stored. (size_t)
|
34
|
+
* -1 means no limit */
|
35
|
+
time_t last_clean; /*< Time when last clean operation ran. */
|
36
|
+
} Struct;
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Gets the associated structure.
|
40
|
+
* @param[in] self self
|
41
|
+
* @param[out] var name of the variable that'll contain the pointer to the
|
42
|
+
* structure.
|
43
|
+
*/
|
44
|
+
#define GET_STRUCT(self, var) \
|
45
|
+
Struct * var; \
|
46
|
+
Data_Get_Struct(self, Struct, var);
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Removes an item from cache.
|
50
|
+
* @param[in] s struct associated with self.
|
51
|
+
* @param[in] i remove this item.
|
52
|
+
*/
|
53
|
+
static Item * item_remove(Struct * s, Item * i)
|
54
|
+
{
|
55
|
+
/* debug, uncomment to enable :p */
|
56
|
+
/* printf("removing %zd (p: %zd, n: %zd)\n", (size_t)i, (size_t)i->prev, */
|
57
|
+
/* (size_t)i->next); */
|
58
|
+
/* if (i->prev) printf("prev's next: %zd\n", (size_t)i->prev->next); */
|
59
|
+
/* if (i->next) printf("next's prev: %zd\n", (size_t)i->next->prev); */
|
60
|
+
|
61
|
+
if (i->prev)
|
62
|
+
i->prev->next = i->next;
|
63
|
+
else
|
64
|
+
s->head = i->next;
|
65
|
+
|
66
|
+
if (i->next)
|
67
|
+
i->next->prev = i->prev;
|
68
|
+
else
|
69
|
+
s->end = i->prev;
|
70
|
+
|
71
|
+
Item * ret = i->next;
|
72
|
+
free(i);
|
73
|
+
--s->length;
|
74
|
+
++s->removed;
|
75
|
+
return ret;
|
76
|
+
}
|
77
|
+
|
78
|
+
/**
|
79
|
+
* Mark elements as used for Ruby's GC.
|
80
|
+
* @param[in] s struct associated with self.
|
81
|
+
*/
|
82
|
+
static void struct_mark(Struct * s)
|
83
|
+
{
|
84
|
+
time_t tim = time(NULL);
|
85
|
+
Item * i;
|
86
|
+
|
87
|
+
for (i = s->head; i; )
|
88
|
+
{
|
89
|
+
if (i->time > tim || i->time == 0)
|
90
|
+
{
|
91
|
+
rb_gc_mark(i->key);
|
92
|
+
rb_gc_mark(i->value);
|
93
|
+
i = i->next;
|
94
|
+
}
|
95
|
+
else
|
96
|
+
{
|
97
|
+
i = item_remove(s, i);
|
98
|
+
}
|
99
|
+
}
|
100
|
+
s->last_clean = tim;
|
101
|
+
}
|
102
|
+
|
103
|
+
/**
|
104
|
+
* Clears outdated items.
|
105
|
+
* @param[in] s struct associated with self.
|
106
|
+
*/
|
107
|
+
static void list_clean(Struct * s)
|
108
|
+
{
|
109
|
+
time_t tim = time(NULL);
|
110
|
+
if (s->last_clean == tim)
|
111
|
+
return;
|
112
|
+
Item * i;
|
113
|
+
|
114
|
+
for (i = s->head; i; )
|
115
|
+
{
|
116
|
+
if (i->time > tim || i->time == 0)
|
117
|
+
{
|
118
|
+
i = i->next;
|
119
|
+
}
|
120
|
+
else
|
121
|
+
i = item_remove(s, i);
|
122
|
+
}
|
123
|
+
s->last_clean = tim;
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Deletes list from a starting point, Doesn't update <tt>s->head</tt> and
|
128
|
+
* <tt>s->end</tt>
|
129
|
+
* @param[in] s struct associated with self.
|
130
|
+
* @param[in] head start freeing from this item
|
131
|
+
*/
|
132
|
+
static void list_free(Struct * s, Item * head)
|
133
|
+
{
|
134
|
+
Item * i;
|
135
|
+
for (i = head; i; )
|
136
|
+
{
|
137
|
+
Item * n = i->next;
|
138
|
+
++s->removed;
|
139
|
+
free(i);
|
140
|
+
i = n;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Frees every memory associated with this object.
|
146
|
+
* @param[in] s struct associated with self.
|
147
|
+
*/
|
148
|
+
static void struct_free(Struct * s)
|
149
|
+
{
|
150
|
+
list_free(s, s->head);
|
151
|
+
free(s);
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Moves the selected Item to the begin of the list chain.
|
156
|
+
* @param[in] s struct associated with self.
|
157
|
+
* @param[in] i: the item to move
|
158
|
+
*/
|
159
|
+
static void item_bring_front(Struct * s, Item * i)
|
160
|
+
{
|
161
|
+
if (!i->prev) return;
|
162
|
+
|
163
|
+
i->prev->next = i->next;
|
164
|
+
if (i->next)
|
165
|
+
i->next->prev = i->prev;
|
166
|
+
else
|
167
|
+
s->end = i->prev;
|
168
|
+
|
169
|
+
s->head->prev = i;
|
170
|
+
i->next = s->head;
|
171
|
+
s->head = i;
|
172
|
+
i->prev = NULL;
|
173
|
+
}
|
174
|
+
|
175
|
+
/*
|
176
|
+
* @return [Number] number of cache hits.
|
177
|
+
*/
|
178
|
+
static VALUE method_get_hits(VALUE self)
|
179
|
+
{
|
180
|
+
GET_STRUCT(self, s);
|
181
|
+
return INT2NUM(s->hits);
|
182
|
+
}
|
183
|
+
|
184
|
+
/*
|
185
|
+
* @return [Number] number of cache misses.
|
186
|
+
*/
|
187
|
+
static VALUE method_get_misses(VALUE self)
|
188
|
+
{
|
189
|
+
GET_STRUCT(self, s);
|
190
|
+
return INT2NUM(s->misses);
|
191
|
+
}
|
192
|
+
|
193
|
+
/*
|
194
|
+
* @return [Number] number of items that has been stored up to now.
|
195
|
+
*/
|
196
|
+
static VALUE method_get_stored(VALUE self)
|
197
|
+
{
|
198
|
+
GET_STRUCT(self, s);
|
199
|
+
return INT2NUM(s->stored);
|
200
|
+
}
|
201
|
+
|
202
|
+
/*
|
203
|
+
* @return [Number] number of items that has been removed from cache until now.
|
204
|
+
*/
|
205
|
+
static VALUE method_get_removed(VALUE self)
|
206
|
+
{
|
207
|
+
GET_STRUCT(self, s);
|
208
|
+
return INT2NUM(s->removed);
|
209
|
+
}
|
210
|
+
|
211
|
+
/*
|
212
|
+
* @return [Number, nil] maximum allowed number of stored items, or +nil+ if
|
213
|
+
* there's no such limit,
|
214
|
+
*/
|
215
|
+
static VALUE method_get_max_items(VALUE self)
|
216
|
+
{
|
217
|
+
GET_STRUCT(self, s);
|
218
|
+
if (s->max_items == (size_t) -1)
|
219
|
+
return Qnil;
|
220
|
+
else
|
221
|
+
return INT2NUM(s->max_items);
|
222
|
+
}
|
223
|
+
|
224
|
+
/* @overload max_items= (max_item)
|
225
|
+
* Set the maximum allowed number of stored items. If the new limit is smaller
|
226
|
+
* than the number of already stored elements, the list will automatically
|
227
|
+
* truncated.
|
228
|
+
* @param [Number, nil] max_items maximum allowed number of stored items, or
|
229
|
+
* +nil+ if there's no such limit,
|
230
|
+
* @return [Number, nil] *max_items*
|
231
|
+
*/
|
232
|
+
static VALUE method_set_max_items(VALUE self, VALUE max_items)
|
233
|
+
{
|
234
|
+
GET_STRUCT(self, s);
|
235
|
+
if (max_items == Qnil)
|
236
|
+
s->max_items = -1;
|
237
|
+
else
|
238
|
+
s->max_items = NUM2ULONG(max_items);
|
239
|
+
|
240
|
+
if (s->length > s->max_items)
|
241
|
+
list_clean(s);
|
242
|
+
if (s->length > s->max_items)
|
243
|
+
{
|
244
|
+
int i = 0;
|
245
|
+
Item * it = s->head;
|
246
|
+
for (; i < s->max_items; it = it->next, ++i);
|
247
|
+
it->prev->next = NULL;
|
248
|
+
s->end = it->prev;
|
249
|
+
list_free(s, it);
|
250
|
+
s->length = s->max_items;
|
251
|
+
}
|
252
|
+
|
253
|
+
return method_get_max_items(self);
|
254
|
+
}
|
255
|
+
|
256
|
+
/*
|
257
|
+
* @return [Number] number of cached elements.
|
258
|
+
*/
|
259
|
+
static VALUE method_get_length(VALUE self)
|
260
|
+
{
|
261
|
+
GET_STRUCT(self, s);
|
262
|
+
return INT2NUM(s->length);
|
263
|
+
}
|
264
|
+
|
265
|
+
/* @overload add(key, value, timeout)
|
266
|
+
* Add a new value to cache.
|
267
|
+
* @param [#eql?] key an unique identifier (in the cache)
|
268
|
+
* @param value the value
|
269
|
+
* @param [Number, nil] timeout time in seconds after the value will be purged
|
270
|
+
* from cache. +nil+ if there's no such limit.
|
271
|
+
* @return *value*
|
272
|
+
*/
|
273
|
+
static VALUE method_add(VALUE self, VALUE key, VALUE val, VALUE timeout)
|
274
|
+
{
|
275
|
+
GET_STRUCT(self, s);
|
276
|
+
|
277
|
+
Item * i;
|
278
|
+
for (i = s->head; i; )
|
279
|
+
{
|
280
|
+
if (rb_funcall(i->key, CMP, 1, key) == Qtrue)
|
281
|
+
{
|
282
|
+
item_bring_front(s, i);
|
283
|
+
i->value = val;
|
284
|
+
if (timeout == Qnil)
|
285
|
+
i->time = 0;
|
286
|
+
else
|
287
|
+
i->time = time(NULL) + NUM2ULONG(timeout);
|
288
|
+
return val;
|
289
|
+
}
|
290
|
+
i = i->next;
|
291
|
+
}
|
292
|
+
|
293
|
+
if (s->length >= s->max_items)
|
294
|
+
list_clean(s);
|
295
|
+
if (s->length >= s->max_items)
|
296
|
+
item_remove(s, s->end);
|
297
|
+
|
298
|
+
i = ALLOC(Item);
|
299
|
+
i->key = key;
|
300
|
+
i->value = val;
|
301
|
+
if (timeout == Qnil)
|
302
|
+
i->time = 0;
|
303
|
+
else
|
304
|
+
i->time = time(NULL) + NUM2ULONG(timeout);
|
305
|
+
i->next = s->head;
|
306
|
+
if (s->head)
|
307
|
+
s->head->prev = i;
|
308
|
+
else
|
309
|
+
s->end = i;
|
310
|
+
i->prev = NULL;
|
311
|
+
s->head = i;
|
312
|
+
++s->stored;
|
313
|
+
++s->length;
|
314
|
+
|
315
|
+
return val;
|
316
|
+
}
|
317
|
+
|
318
|
+
/*
|
319
|
+
* Tries to get a value from the cache.
|
320
|
+
* @overload get(key, default = nil)
|
321
|
+
* Tries to get a value from cache. If it fails, returns a default value.
|
322
|
+
* @param [#eql?] key the key of the value to get
|
323
|
+
* @param default default return value.
|
324
|
+
* @return the value from cache if found, *default* otherwise
|
325
|
+
* @overload get(key)
|
326
|
+
* Tries to get a value from cache. If it fails, executes the block.
|
327
|
+
* @param [#eql?] key the key of the value to get
|
328
|
+
* @yield if key is not found.
|
329
|
+
* @return the value from cache if found, or yielded block's return value.
|
330
|
+
*/
|
331
|
+
static VALUE method_get(int argc, VALUE * argv, VALUE self)
|
332
|
+
{
|
333
|
+
GET_STRUCT(self, s);
|
334
|
+
VALUE key, default_val;
|
335
|
+
rb_scan_args(argc, argv, "11", &key, &default_val);
|
336
|
+
|
337
|
+
time_t tim = time(NULL);
|
338
|
+
Item * i;
|
339
|
+
for (i = s->head; i; i = i->next)
|
340
|
+
{
|
341
|
+
if (i->time <= tim && i->time != 0) continue;
|
342
|
+
if (rb_funcall(i->key, CMP, 1, key) == Qtrue)
|
343
|
+
{
|
344
|
+
++s->hits;
|
345
|
+
item_bring_front(s, i);
|
346
|
+
return i->value;
|
347
|
+
}
|
348
|
+
}
|
349
|
+
|
350
|
+
++s->misses;
|
351
|
+
if (rb_block_given_p())
|
352
|
+
{
|
353
|
+
return rb_yield(Qnil);
|
354
|
+
}
|
355
|
+
return default_val;
|
356
|
+
}
|
357
|
+
|
358
|
+
/*
|
359
|
+
* Removes a given key or keys from the cache.
|
360
|
+
* @overload remove(key)
|
361
|
+
* @param key remove this key from the cache
|
362
|
+
* @overload remove() { |key, value| ... }
|
363
|
+
* @yield [key, value] for each item in the cache,
|
364
|
+
* @yieldparam key key of the current item.
|
365
|
+
* @yieldparam value value associated with the *key*.
|
366
|
+
* @yieldreturn [Boolean] +true+ if you want to delete this item
|
367
|
+
* @return [nil]
|
368
|
+
*/
|
369
|
+
static VALUE method_remove(int argc, VALUE * argv, VALUE self)
|
370
|
+
{
|
371
|
+
GET_STRUCT(self, s);
|
372
|
+
VALUE todel;
|
373
|
+
rb_scan_args(argc, argv, "01", &todel);
|
374
|
+
time_t tim = time(NULL);
|
375
|
+
int has_block = rb_block_given_p();
|
376
|
+
|
377
|
+
Item * i;
|
378
|
+
for (i = s->head; i; )
|
379
|
+
{
|
380
|
+
VALUE to_del;
|
381
|
+
if (i->time <= tim && i->time != 0) to_del = Qtrue;
|
382
|
+
else if (has_block)
|
383
|
+
{
|
384
|
+
VALUE ary = rb_ary_new3(2, i->key, i->value);
|
385
|
+
to_del = rb_yield(ary);
|
386
|
+
}
|
387
|
+
else
|
388
|
+
to_del = rb_funcall(i->key, CMP, 1, todel);
|
389
|
+
|
390
|
+
if (to_del != Qfalse && to_del != Qnil)
|
391
|
+
i = item_remove(s, i);
|
392
|
+
else
|
393
|
+
i = i->next;
|
394
|
+
}
|
395
|
+
|
396
|
+
return Qnil;
|
397
|
+
}
|
398
|
+
|
399
|
+
/*
|
400
|
+
* Clears the cache.
|
401
|
+
* @return [nil]
|
402
|
+
*/
|
403
|
+
static VALUE method_clear(VALUE self)
|
404
|
+
{
|
405
|
+
GET_STRUCT(self, s);
|
406
|
+
list_free(s, s->head);
|
407
|
+
s->head = s->end = NULL;
|
408
|
+
s->length = 0;
|
409
|
+
|
410
|
+
return Qnil;
|
411
|
+
}
|
412
|
+
|
413
|
+
/* @overload new(max_items = nil)
|
414
|
+
* Creates a new list based DCache backend.
|
415
|
+
* @param [Number, nil] max_items maximum number of stored items, +nil+ if
|
416
|
+
* there's no such limit.
|
417
|
+
* @return [DCacheList] a new object.
|
418
|
+
*/
|
419
|
+
static VALUE method_new(int argc, VALUE * argv, VALUE class)
|
420
|
+
{
|
421
|
+
VALUE max_items;
|
422
|
+
rb_scan_args(argc, argv, "01", &max_items);
|
423
|
+
|
424
|
+
Struct * s;
|
425
|
+
s = ALLOC(Struct);
|
426
|
+
s->head = s->end = NULL;
|
427
|
+
s->hits = s->misses = 0;
|
428
|
+
s->stored = s->removed = 0;
|
429
|
+
if (max_items == Qnil)
|
430
|
+
s->max_items = -1;
|
431
|
+
else
|
432
|
+
s->max_items = NUM2ULONG(max_items);
|
433
|
+
s->length = 0;
|
434
|
+
s->last_clean = 0;
|
435
|
+
|
436
|
+
VALUE x = Data_Wrap_Struct(class, struct_mark, struct_free, s);
|
437
|
+
rb_obj_call_init(x, 0, NULL);
|
438
|
+
|
439
|
+
return x;
|
440
|
+
}
|
441
|
+
|
442
|
+
void Init_dcache_list()
|
443
|
+
{
|
444
|
+
CMP = rb_intern("eql?");
|
445
|
+
|
446
|
+
VALUE dcache = rb_define_class("DCacheList", rb_cObject);
|
447
|
+
rb_define_singleton_method(dcache, "new", method_new, -1);
|
448
|
+
rb_define_method(dcache, "add", method_add, 3);
|
449
|
+
rb_define_method(dcache, "get", method_get, -1);
|
450
|
+
rb_define_method(dcache, "remove", method_remove, -1);
|
451
|
+
rb_define_method(dcache, "clear", method_clear, 0);
|
452
|
+
|
453
|
+
rb_define_method(dcache, "hits", method_get_hits, 0);
|
454
|
+
rb_define_method(dcache, "misses", method_get_misses, 0);
|
455
|
+
rb_define_method(dcache, "stored", method_get_stored, 0);
|
456
|
+
rb_define_method(dcache, "removed", method_get_removed, 0);
|
457
|
+
rb_define_method(dcache, "max_items", method_get_max_items, 0);
|
458
|
+
rb_define_method(dcache, "max_items=", method_set_max_items, 1);
|
459
|
+
rb_define_method(dcache, "length", method_get_length, 0);
|
460
|
+
}
|
data/lib/dcache.rb
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
# Main DCache class
|
2
|
+
class DCache
|
3
|
+
# DCache's version
|
4
|
+
VERSION = '0.0.0'
|
5
|
+
|
6
|
+
protected
|
7
|
+
# Declares a reader to the underlying backend.
|
8
|
+
def self.embedded_reader *args
|
9
|
+
args.flatten.each do |v|
|
10
|
+
class_eval <<EOS
|
11
|
+
def #{v}
|
12
|
+
@backend.#{v}
|
13
|
+
end
|
14
|
+
EOS
|
15
|
+
end
|
16
|
+
end
|
17
|
+
public
|
18
|
+
|
19
|
+
# @return [Symbol] Which DCache backend is used (can be +:list+ or +:ary+
|
20
|
+
# currently)
|
21
|
+
attr_reader :backend_type
|
22
|
+
# Current backend in use.
|
23
|
+
attr_reader :backend
|
24
|
+
|
25
|
+
# @return [Number] Number of cache hits
|
26
|
+
embedded_reader :hits
|
27
|
+
# @return [Number] Number of cache misses
|
28
|
+
embedded_reader :misses
|
29
|
+
# @return [Number] Number of elements stored (only if
|
30
|
+
# <tt>backend == :list</tt>)
|
31
|
+
embedded_reader :stored
|
32
|
+
alias :added :stored
|
33
|
+
# @return [Number] Number of elements removed from cache (only if
|
34
|
+
# <tt>backend == :list</tt>)
|
35
|
+
embedded_reader :removed
|
36
|
+
alias :deleted :removed
|
37
|
+
# @return [Number] Number of elements currently stored in cache
|
38
|
+
embedded_reader :length
|
39
|
+
alias :size :length
|
40
|
+
alias :items :length
|
41
|
+
# @return [Number, nil] Maximum number of elements that can be stored, or
|
42
|
+
# +nil+ if there's no limit.
|
43
|
+
embedded_reader :max_items
|
44
|
+
alias :max_length :max_items
|
45
|
+
alias :max_size :max_items
|
46
|
+
# Set maximum number of elements. Only if <tt>backend == :list</tt>.
|
47
|
+
# @param [Number, nil] val new maximum number of elements. If it's smaller
|
48
|
+
# than the amount of currently stored elements, old elements will be
|
49
|
+
# eliminated. If it's +nil+, there's no maximum.
|
50
|
+
# @return [Number, nil] parameter *val*
|
51
|
+
def max_items= val
|
52
|
+
@backend.max_items = val
|
53
|
+
end
|
54
|
+
alias :max_length= :max_items=
|
55
|
+
alias :max_size= :max_items=
|
56
|
+
|
57
|
+
# @return [Number, nil] Default timeout for new values, or +nil+ if there's
|
58
|
+
# no timeout
|
59
|
+
attr_accessor :timeout
|
60
|
+
|
61
|
+
# Initializes DCache.
|
62
|
+
# @param [Symbol] backend which backend to use. Currently can be +:ary+ or
|
63
|
+
# +:list+. +:ary+ is based on a fixed size array, and implements something
|
64
|
+
# like "Least Recently Added". +:list+ uses a linked list to store the
|
65
|
+
# elements, so it's maximum size can be changed during runtime, and it
|
66
|
+
# implements a simple LRU cache.
|
67
|
+
# @param [ ] args optional parameters passed to backend initialization. See
|
68
|
+
# {DCacheAry} and {DCacheList}.
|
69
|
+
def initialize backend = :list, *args
|
70
|
+
require "dcache_#{backend}"
|
71
|
+
@backend_type = backend
|
72
|
+
@backend = Kernel.const_get("DCache#{backend.capitalize}").new(*args)
|
73
|
+
@timeout = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Tries to get a value from the cache.
|
77
|
+
# @overload get(key, default = nil)
|
78
|
+
# Tries to get a value from cache. If it fails, returns a default value.
|
79
|
+
# @param [#eql?] key the key of the value to get
|
80
|
+
# @param default default return value.
|
81
|
+
# @return the value from cache if found, *default* otherwise
|
82
|
+
# @overload get(key) { ... }
|
83
|
+
# Tries to get a value from cache. If it fails, executes the block.
|
84
|
+
# @param [#eql?] key the key of the value to get
|
85
|
+
# @yield if key is not found.
|
86
|
+
# @return the value from cache if found, or yielded block's return value.
|
87
|
+
def get key, default = nil
|
88
|
+
if block_given?
|
89
|
+
@backend.get(key) { yield }
|
90
|
+
else
|
91
|
+
@backend.get key, default
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Tries to get a value. If it fails, add it, then return.
|
96
|
+
# @overload get_or_add(key, value, timeout = @timeout)
|
97
|
+
# @param [#eql?] key the key of the value to get
|
98
|
+
# @param value value to set if get fails. If a block is passed, it'll be
|
99
|
+
# evaluated and used instead of this
|
100
|
+
# @param [Number, nil] timeout of the newly added item, or +nil+ if there's
|
101
|
+
# no timeout.
|
102
|
+
# @overload get_or_add(key, timeout = @timeout) { ... }
|
103
|
+
# @param [#eql?] key the key of the value to get
|
104
|
+
# @param [Number, nil] timeout of the newly added item, or +nil+ if there's
|
105
|
+
# no timeout.
|
106
|
+
# @yield if *key* is not found
|
107
|
+
# @yieldreturn value for the key, that will be added to cache.
|
108
|
+
# @return value from cache that it either already had, or has been added.
|
109
|
+
def get_or_add key, *plus
|
110
|
+
@backend.get key do
|
111
|
+
if block_given?
|
112
|
+
value = yield
|
113
|
+
timeout = plus[0] or @timeout
|
114
|
+
else
|
115
|
+
value = plus[0]
|
116
|
+
timeout = plus[1] or @timeout
|
117
|
+
end
|
118
|
+
@backend.add key, value, timeout
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Tries to get a value from the cache, or raise an exception if it fails.
|
123
|
+
# @param [#eql?] key the key to get
|
124
|
+
# @return the value from cache.
|
125
|
+
# @raise ArgumentError if *key* is not found in cache.
|
126
|
+
def get_or_raise key
|
127
|
+
@backend.get key do
|
128
|
+
raise ArgumentError, "#{key} is not cached"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Adds an item to the cache
|
133
|
+
# @param [#eql?] key an unique identifier (in the cache)
|
134
|
+
# @param value the value
|
135
|
+
# @param [Number, nil] timeout time in seconds after the value will be purged
|
136
|
+
# from cache. +nil+ if there's no such limit.
|
137
|
+
# @return *value*
|
138
|
+
def add key, value, timeout = @timeout
|
139
|
+
@backend.add key, value, timeout
|
140
|
+
end
|
141
|
+
|
142
|
+
# Removes a given key or keys from the cache.
|
143
|
+
# @overload remove(key)
|
144
|
+
# @param key remove this key from the cache
|
145
|
+
# @overload remove() { |key, value| ... }
|
146
|
+
# @yield [key, value] for each item in the cache,
|
147
|
+
# @yieldparam key key of the current item.
|
148
|
+
# @yieldparam value value associated with the *key*.
|
149
|
+
# @yieldreturn [Boolean] +true+ if you want to delete this item
|
150
|
+
# @return [nil]
|
151
|
+
def remove key = nil
|
152
|
+
if block_given?
|
153
|
+
@backend.remove { |k,v| yield k,v }
|
154
|
+
else
|
155
|
+
@backend.remove key
|
156
|
+
end
|
157
|
+
end
|
158
|
+
alias :delete :remove
|
159
|
+
alias :invalidate :delete
|
160
|
+
|
161
|
+
# Converts the cache to a hash containing all stored values as key-value
|
162
|
+
# pairs.
|
163
|
+
# @return [Hash] all cached entries as a hash.
|
164
|
+
def to_hash
|
165
|
+
h = {}
|
166
|
+
@backend.remove do |k, v|
|
167
|
+
h[k] = v unless h.has_key? k
|
168
|
+
false
|
169
|
+
end
|
170
|
+
h
|
171
|
+
end
|
172
|
+
|
173
|
+
# Clears the cache (removes all stored items)
|
174
|
+
# @return [nil]
|
175
|
+
def clear
|
176
|
+
@backend.clear
|
177
|
+
end
|
178
|
+
alias :erase :clear
|
179
|
+
|
180
|
+
# Overridden inspect to list info from underlying backend aswell.
|
181
|
+
# @return [String]
|
182
|
+
def inspect
|
183
|
+
s = "#<#{self.class.to_s} "
|
184
|
+
s << get_variables_ary.map do |i|
|
185
|
+
ss ="#{i}="
|
186
|
+
if (i[0] == '@') then
|
187
|
+
ss << instance_variable_get(i).inspect
|
188
|
+
else
|
189
|
+
ss << send(i).inspect
|
190
|
+
end
|
191
|
+
end.join(', ')
|
192
|
+
s << '>'
|
193
|
+
end
|
194
|
+
|
195
|
+
def pretty_print p
|
196
|
+
p.object_group(self) do
|
197
|
+
ary = get_variables_ary
|
198
|
+
p.seplist(ary, lambda { p.text ','}) do |i|
|
199
|
+
p.breakable
|
200
|
+
p.text "#{i}"
|
201
|
+
p.text '='
|
202
|
+
p.group(1) {
|
203
|
+
p.breakable ''
|
204
|
+
if i[0] == '@' then
|
205
|
+
p.pp(self.instance_variable_get(i))
|
206
|
+
else
|
207
|
+
p.pp(self.send(i))
|
208
|
+
end
|
209
|
+
}
|
210
|
+
end
|
211
|
+
p.text ','
|
212
|
+
p.breakable
|
213
|
+
p.text "items="
|
214
|
+
p.group(1, '{', '}') {
|
215
|
+
prev = false
|
216
|
+
@backend.remove do |k,v|
|
217
|
+
p.text ',' if prev
|
218
|
+
prev = true
|
219
|
+
|
220
|
+
p.breakable
|
221
|
+
p.group(1) {
|
222
|
+
p.pp(k)
|
223
|
+
}
|
224
|
+
# non-standard, but i like better that way
|
225
|
+
p.text ' => '
|
226
|
+
p.group(1) {
|
227
|
+
p.pp(v)
|
228
|
+
}
|
229
|
+
false
|
230
|
+
end
|
231
|
+
}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
# @return [Array<Symbol, String>] list of instance and embedded variables.
|
237
|
+
def get_variables_ary
|
238
|
+
ary = ['@backend_type', '@backend', '@timeout', :length, :max_length,
|
239
|
+
:hits, :misses]
|
240
|
+
ary += [:stored, :removed] if @backend_type == :list
|
241
|
+
ary
|
242
|
+
end
|
243
|
+
end
|