import functools
import logging
import sys
import threading
import time

log = logging.getLogger('ldap-web.lru')

def locked(f):
    @functools.wraps(f)
    def func(self, *a, **kw):
        self.lock.acquire()
        try:
            return f(self, *a, **kw)
        finally:
            self.lock.release()
    return func

class LRUPool(threading.Thread):
    """A key-value pool to store objects with a timeout.

    Consumers of objects can register callbacks which will be called on
    object expiry. Expiry is least-recently-used - any access reset the
    time counter."""
    def __init__(self, timeout=60.0, **kw):
        threading.Thread.__init__(self, **kw)
        self.setDaemon(True)
        self.pool = {}
        self.timeout = timeout
        self.lock = threading.Lock()
        self.callbacks = {}
    def run(self):
        log.info("Pool {} starting.", self)
        while True:
            time.sleep(self.timeout / 2)
            self.lock.acquire()
            now = time.time()
            drop = set()
            for k, [c, atime] in self.pool.items():
                if now - atime > self.timeout:
                    log.info("Pool {} dropping {}.", self, k)
                    drop.add(k)

            for k in list(drop):
                self._drop(k)
            self.lock.release()
    def register_callback(self, action, cb):
        self.callbacks.setdefault(action, []).append(cb)
    @locked
    def __getitem__(self, key):
        item = self.pool.get(key)
        if not item:
            return
        item[1] = time.time()
        return item[0]
    def _insert(self, key, item):
        self.pool[key] = [item, time.time()]
        return item
    @locked
    def insert(self, key, item):
        return self._insert(key, item)
    def _drop(self, key):
        for f in self.callbacks.get('drop',[]):
            f(key)
        return self.pool.pop(key, None)
    @locked
    def drop(self, key):
        return self._drop(key)


