import shelve
import threading
from UserDict import DictMixin

__version__ = "0.10.2"


class BackendError(Exception):
    pass

class CheckOutError(Exception):
    pass

class CommitError(Exception):
    pass


class ShelveBackend(DictMixin):
    def __init__(self, dbfile=None, cache=False):
        self.dbfile = dbfile
        if not self.dbfile:
            raise BackendError, "Need to supply a database filename."
        self.lock = threading.RLock()
        self.db = shelve.open(dbfile)
        self.db.close()
        if cache:
            self.cache = {}

        self.checkedOut = []            
        self.keyLocks = {}

    def openDB(self, mode='r'):
        try:
            self.lock.acquire()
            self.db = shelve.open(self.dbfile, mode)
        except:
            self.lock.release()
            raise

    def closeDB(self):
        try:
            self.db.close()
        finally:
            self.lock.release()

    def checkout(self, key):
        keyLock = self.keyLocks.setdefault(key, threading.Lock())
        keyLock.acquire()
        # rebind the keyLock to the key in case another thread has deleted
        # it from beneath us!
        self.keyLocks.setdefault(key, keyLock)
        try:
            val = self.__getitem__(key)
            return val
        except:
            keyLock.release()

    def cancel(self, key):
        if key in self.keyLocks:
            keyLock = self.keyLocks[key]
            del self.keyLocks[key]
            keyLock.release()
        else:
            raise CheckOutError, '"%s" is not checked out' % key

    def commit(self, key, value):
        if key in self.keyLocks:
            keyLock = self.keyLocks[key]
            try:
                self.__setitem__(key, value)
            finally:
                del self.keyLocks[key]
                keyLock.release()
        else:
            raise CommitError, '"%s" needs to be checked out first' % key

    def delete(self, key):
        if key in self.keyLocks:
            keyLock = self.keyLocks[key]
            try:
                self.__delitem__(key)
            finally:
                del self.keyLocks[key]
                keyLock.release()
        else:
            raise CommitError, '"%s" needs to be checked out first' % key
    
    def __getitem__(self, key):
        if hasattr(self, 'cache'):
            try:
                val = self.cache[key]
                return val
            except KeyError:
                pass
        self.openDB()
        try:
            val = self.db[key]
            if hasattr(self, 'cache'):
                self.cache[key] = val
            return val
        finally:
            self.closeDB()


    def __setitem__(self, key, value):
        self.openDB(mode='w')
        try:
            if hasattr(self, 'cache'):
                self.cache[key] = value
            self.db[key] = value
        finally:
            self.closeDB()

    def __delitem__(self, key):
        self.openDB(mode='w')
        try:
            if hasattr(self, 'cache'):
                del self.cache[key]
            del self.db[key]
        finally:
            self.closeDB()

    def keys(self):
        self.openDB()
        keys = self.db.keys()
        self.closeDB()
        return keys
