Merge 0.10->trunk
[prosody.git] / util / cache.lua
index 9453697ce8a00459afe152d53bdc2af3e6b2e5ca..44bbfe3098427ae9c9fef7ba9b6ce8d9a4830ddf 100644 (file)
@@ -1,11 +1,3 @@
-local cache_methods = {};
-local cache_mt = { __index = cache_methods };
-
-local function new(size)
-       assert(size > 0, "cache size must be greater than zero");
-       local data = {};
-       return setmetatable({ data = data, count = 0, size = size, head = nil, tail = nil }, cache_mt);
-end
 
 local function _remove(list, m)
        if m.prev then
@@ -14,29 +6,32 @@ local function _remove(list, m)
        if m.next then
                m.next.prev = m.prev;
        end
-       if list.tail == m then
-               list.tail = m.prev;
+       if list._tail == m then
+               list._tail = m.prev;
        end
-       if list.head == m then
-               list.head = m.next;
+       if list._head == m then
+               list._head = m.next;
        end
-       list.count = list.count - 1;
+       list._count = list._count - 1;
 end
 
 local function _insert(list, m)
-       if list.head then
-               list.head.prev = m;
+       if list._head then
+               list._head.prev = m;
        end
-       m.prev, m.next = nil, list.head;
-       list.head = m;
-       if not list.tail then
-               list.tail = m;
+       m.prev, m.next = nil, list._head;
+       list._head = m;
+       if not list._tail then
+               list._tail = m;
        end
-       list.count = list.count + 1;
+       list._count = list._count + 1;
 end
 
+local cache_methods = {};
+local cache_mt = { __index = cache_methods };
+
 function cache_methods:set(k, v)
-       local m = self.data[k];
+       local m = self._data[k];
        if m then
                -- Key already exists
                if v ~= nil then
@@ -47,27 +42,34 @@ function cache_methods:set(k, v)
                else
                        -- Remove from list
                        _remove(self, m);
-                       self.data[k] = nil;
+                       self._data[k] = nil;
                end
-               return;
+               return true;
        end
        -- New key
        if v == nil then
-               return;
+               return true;
        end
        -- Check whether we need to remove oldest k/v
-       if self.count == self.size then
-               self.data[self.tail.key] = nil;
-               _remove(self, self.tail);
+       if self._count == self.size then
+               local tail = self._tail;
+               local on_evict, evicted_key, evicted_value = self._on_evict, tail.key, tail.value;
+               if on_evict ~= nil and (on_evict == false or on_evict(evicted_key, evicted_value) == false) then
+                       -- Cache is full, and we're not allowed to evict
+                       return false;
+               end
+               _remove(self, tail);
+               self._data[evicted_key] = nil;
        end
 
        m = { key = k, value = v, prev = nil, next = nil };
-       self.data[k] = m;
+       self._data[k] = m;
        _insert(self, m);
+       return true;
 end
 
 function cache_methods:get(k)
-       local m = self.data[k];
+       local m = self._data[k];
        if m then
                return m.value;
        end
@@ -75,7 +77,7 @@ function cache_methods:get(k)
 end
 
 function cache_methods:items()
-       local m = self.head;
+       local m = self._head;
        return function ()
                if not m then
                        return;
@@ -86,6 +88,64 @@ function cache_methods:items()
        end
 end
 
+function cache_methods:values()
+       local m = self._head;
+       return function ()
+               if not m then
+                       return;
+               end
+               local v = m.value;
+               m = m.next;
+               return v;
+       end
+end
+
+function cache_methods:count()
+       return self._count;
+end
+
+function cache_methods:head()
+       local head = self._head;
+       if not head then return nil, nil; end
+       return head.key, head.value;
+end
+
+function cache_methods:tail()
+       local tail = self._tail;
+       if not tail then return nil, nil; end
+       return tail.key, tail.value;
+end
+
+function cache_methods:table()
+       if not self.proxy_table then
+               self.proxy_table = setmetatable({}, {
+                       __index = function (t, k)
+                               return self:get(k);
+                       end;
+                       __newindex = function (t, k, v)
+                               if not self:set(k, v) then
+                                       error("failed to insert key into cache - full");
+                               end
+                       end;
+                       __pairs = function (t)
+                               return self:items();
+                       end;
+                       __len = function (t)
+                               return self:count();
+                       end;
+               });
+       end
+       return self.proxy_table;
+end
+
+local function new(size, on_evict)
+       size = assert(tonumber(size), "cache size must be a number");
+       size = math.floor(size);
+       assert(size > 0, "cache size must be greater than zero");
+       local data = {};
+       return setmetatable({ _data = data, _count = 0, size = size, _head = nil, _tail = nil, _on_evict = on_evict }, cache_mt);
+end
+
 return {
        new = new;
 }