########################################################################
#
# File Name:      Dbm.py
#
# Documentation:  http://docs.4suite.org/4ODS/StorageManager/Adapters/Dbm.py.html
#
"""
The DBM database back-end adapter.
WWW: http://4suite.org/4ODS         e-mail: support@4suite.org

Copyright (c) 1999 Fourthought, Inc, USA.   All Rights Reserved.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

import string, types, time, os, sys

from Ft.Ods.Exception import FtodsObjectNotFound,FtodsUnknownError
from Ft.Ods.StorageManager.Adapters import Constants

from Ft.Lib import DbmDatabase
from Ft.Lib import Set
from Ft.Ods.StorageManager.Adapters import ClassCache
import DManager

import tempfile

########################################
# NOTE:
# Interfaces in this class are those that every driver must support
#############################################


class DbAdapter:
    
    ################
    # Transactional Interface
    ################

    manager = DManager.DbManager()


    def begin(self,db):
        pass

    def commit(self,db):
        # Save changes
        db.commit()
        db._clear()
        

    def checkpoint(self,db):
        #Save changes, but don't close
        db.checkpoint()

    def abort(self,db):
        # Ignore changes
        db.rollback()
        db._clear()

    ############################
    ###Locking
    ############################

    def lock(self,repoId,mode):
        # Single user only, for now...
        pass

    def try_lock(self,repoId,mode):
        # Single user only, for now...
        pass


    #############################
    #MISC
    #############################
    def objectUptoDate(self,db,typ,id_,testTime):
        if typ == Constants.Types.ROBJECT:
            t = db['ftods_repos']
        elif typ in [Constants.Types.LIST_COLLECTION,
                     Constants.Types.SET_COLLECTION,
                     Constants.Types.BAG_COLLECTION]:
            t = db['ftods_collections']
        elif typ == Constants.Types.POBJECT:
            t = db['ftods_objects']
        elif typ == Constants.Types.DICTIONARY_COLLECTION:
            t = db['ftods_dictionaries']
        else:
            raise FtodsUnknownError(msg="Unknown object type %s" % str(typ))
        if t.has_key(str(id_)):
            return t[str(id_)][2] < testTime
        return 0
    


    ###########################
    #Python Classes
    ###########################
    def newPythonClass(self,db,klass):
        seq = db['ftods_sequences']
        pid = seq['classIds']
        seq['classIds'] = pid + 1
        modName = str(klass.__module__)
        if modName[:8] == '__main__':
            dirs = os.path.splitext(sys.argv[0])[0]
            modName = string.replace(dirs,'/','.') + modName[8:]
        
        classes = db['ftods_classes']
        classes[str(pid)] = (modName, klass.__name__)
        classes[str(hash((modName, klass.__name__)))] = pid
        return pid

    def getPythonClass(self,db,pid):
        classes = db['ftods_classes']
        return classes.get(str(pid),None)

    def deletePythonClass(self,db,pid):
        classes = db['ftods_classes']
        if classes.has_key(str(pid)):
            del classes[str(pid)]

    def getPythonClassId(self,db,klass):
        klassId = ClassCache.g_cache.getClassId(klass)
        if klassId is None:
            modName = str(klass.__module__)
            if modName[:8] == '__main__':
                dirs = os.path.splitext(sys.argv[0])[0]
                modName = string.replace(dirs,'/','.') + modName[8:]
            
            klassId = db['ftods_classes'].get(str(hash((modName, klass.__name__))))
        return klassId

    def _4ods_getPythonClassId(self,db,klass):
        cid = self.getPythonClassId(db,klass)
        if cid is None:
            raise FtodsUnknownError(msg="Unknown Class %s, did you register it?" % str(klass))
        return cid

    ###########################
    #Python Classes of Literals
    ###########################

    def newPythonLiteralClass(self,db,typ,repoId,klass):
        modName = str(klass.__module__)
        if modName[:8] == '__main__':
            dirs = os.path.splitext(sys.argv[0])[0]
            modName = string.replace(dirs,'/','.') + modName[8:]
        classes = db['ftods_literalClasses']
        classes[str((typ,repoId))] = (modName, klass.__name__)
        
    modifyPythonLiteralClass = newPythonLiteralClass

    def getPythonLiteralClass(self,db,typ,repoId):
        classes = db['ftods_literalClasses']
        return classes.get(str((typ,repoId)),None)

    def deletePythonLiteralClass(self,db,typ,repoId):
        classes = db['ftods_literalClasses']
        if classes.has_key(str((typ,repoId))):
            del classes[str((typ,repoId))]


    #####################
    #Repository Management
    #####################
    def newRepositoryObjectId(self,db,metaKind,klass,rid = None):
        if rid == None:
            seq = db['ftods_sequences']
            rid = seq['repoIds']
            seq['repoIds'] = rid + 1
        klassId = self._4ods_getPythonClassId(db,klass)
        repos = db['ftods_repos']
        repos[str(rid)] = [klassId,[None]*len(klass._tupleTypes),None]
        return rid


    #Repository Object Helpers
    def newInterfaceStorage(self,db,interface):
        pass


    def writeRepositoryObject(self,db,metaKind,rid,types,names,values):
        repos = db['ftods_repos']
        for ctr in range(len(values)):
            if values[ctr] is not None:
                repos[str(rid)][1][ctr] = values[ctr]
        repos[str(rid)][2] = time.time()
        return 1

    def getRepositoryObject(self,db,rid):
        repos = db['ftods_repos']
        data = repos.get(str(rid))
        if data is None:
            return data
        kid = int(data[0])
        klass = ClassCache.g_cache.getClass(self,db,kid)
        return (klass,data[1])

    def deleteRepositoryObject(self,db,rid,metaKind):
        repos = db['ftods_repos']
        if repos.has_key(str(rid)):
            del repos[str(rid)]


    #############################
    #Object Interface
    #############################
    def newObjectId(self,db,klass):
        seq = db['ftods_sequences']
        oid = seq['objIds']
        seq['objIds'] = oid + 1
        klassId = self._4ods_getPythonClassId(db,klass)
        objs = db['ftods_objects']
        objs[str(oid)] = [klassId,[None]*len(klass._tupleTypes),None]
        return oid

    def writeObject(self,db,oid,typeId,types,names,values):
        objs = db['ftods_objects']
        for ctr in range(len(values)):
            if values[ctr] is not None:
                objs[str(oid)][1][ctr] = values[ctr]
        objs[str(oid)][2] = time.time()
        return 1

    def getObject(self,db,oid):
        objs = db['ftods_objects']
        data = objs.get(str(oid))
        if data is None:
            return data
        kid = int(data[0])
        klass = ClassCache.g_cache.getClass(self,db,kid)
        return (klass,data[1])


    def deleteObject(self,db,oid,klass):
        objs = db['ftods_objects']
        if objs.has_key(str(oid)):
            del objs[str(oid)]


    def getAllObjectIds(self,db):
        return map(int, db['ftods_objects'].keys()) 



    ###########################
    #Extent interface
    ###########################
    def addExtent(self,db,name,persistentType):
        bn = db['ftods_extents']
        bn[name] = (persistentType,[])

    def dropExtent(self,db,name):
        bn = db['ftods_extents']
        if bn.has_key(name):
            del bn[name]

    def addExtentEntry(self,db,names,oid):
        bn = db['ftods_extents']
        for name in names:
            bn[name][1].append(oid)

    def dropExtentEntry(self,db,names,oid):
        bn = db['ftods_extents']
        for name in names:
            if oid in bn[name][1]:
                bn[name][1].remove(oid)

    def extentNames(self,db):
        return db['ftods_extents'].keys()

    def loadExtent(self,db,name):
        return db['ftods_extents'].get(name,None)

    ###########################
    #Binding interface
    ###########################
    def bind(self,db,oid,name):
        bn = db['ftods_boundNames']
        bn[name] = oid

    def unbind(self,db,name):
        bn = db['ftods_boundNames']
        if bn.has_key(name):
            del bn[name]

    def getAllBindings(self,db):
        return db['ftods_boundNames'].keys()

    def getObjectBindings(self,db,oid):
        #This will be slow
        bindings = []
        for name,o in db['ftods_boundNames'].items():
            if oid == o:
                bindings.append(name)
        return bindings

    def lookup(self,db,name):
        return db['ftods_boundNames'].get(name,None)
        

    ############################
    #Collection Interface
    ###########################
    def newCollection(self,db,klass,collType,subType,subTypeRepoId):
        seq = db['ftods_sequences']
        cid = seq['collectionIds']
        seq['collectionIds'] = cid + 1

        klassId = self._4ods_getPythonClassId(db,klass)
        cols = db['ftods_collections']
        cols[str(cid)] = [klassId,[],None,subType,subTypeRepoId]
        return cid

    def writeCollection(self,db,cid,changes,collType,subType):

        col = db['ftods_collections'][str(cid)]
        for change in changes:
            if change[0] in [Constants.CollectionChanges.INSERT,Constants.CollectionChanges.APPEND]:
                values = change[1]
                for value,index in values:
                    if change[0] == Constants.CollectionChanges.INSERT:
                        #Shift all other indexs
                        col[1].insert(index,value)
                    else:
                        col[1].append(value)
            else:
                for value,index in change[1]:
                    del col[1][index]
        col[2] = time.time()
        return 1

    def getCollection(self,db,cid):
        col = db['ftods_collections'].get(str(cid))
        if not col:
            return None

        klass = ClassCache.g_cache.getClass(self,db,col[0])

        #Interface requires a dictionary to be returned
        res = {}
        for ctr in range(len(col[1])):
            res[ctr] = col[1][ctr]
        
        return (klass,col[3],col[4],res)

    def deleteCollection(self,db,cid,collType,subType):
        cols = db['ftods_collections']
        if cols.has_key(str(cid)):
            del cols[str(cid)]

    ############################
    #Dictionary Interface
    ###########################
    def newDictionary(self,db,klass,keyType,keyTypeRepoId,subType,subTypeRepoId):
        seq = db['ftods_sequences']
        did = seq['dictionaryIds']
        seq['dictionaryIds'] = did + 1

        klassId = self._4ods_getPythonClassId(db,klass)
        dicts = db['ftods_dictionaries']
        dicts[str(did)] = [klassId,{},None,keyType,keyTypeRepoId,subType,subTypeRepoId]
        return did

    def writeDictionary(self,db,did,changes,keyType,subType):

        dict = db['ftods_dictionaries'][str(did)]

        for change,name,value in changes:
            if change in [Constants.DictionaryChanges.ADD,Constants.DictionaryChanges.CHANGE]:
                dict[1][name] = value
            else:
                del dict[1][name]

        dict[2] = time.time()
        db['ftods_dictionaries'][str(did)] = dict
        return 1

    def getDictionary(self,db,did):
        dict = db['ftods_dictionaries'].get(str(did))
        if not dict:
            return None

        klass = ClassCache.g_cache.getClass(self,db,dict[0])
        return (klass,dict[3],dict[4],dict[5],dict[6],dict[1].copy())

    def deleteDictionary(self,db,did,keyType,subType):
        dicts = db['ftods_dictionaries']
        if dicts.has_key(str(did)):
            del dicts[str(did)]

        


    #######################################
    #Blobs
    #######################################
    def newBlob(self,db):
        seq = db['ftods_sequences']
        bid = seq['blobIds']
        seq['blobIds'] = bid + 1
        bs = db['ftods_blobs']
        bs[str(bid)] = ''
        return bid

    def readBlob(self,db,bid):
        return db['ftods_blobs'].get(str(bid),None)

    def writeBlob(self,db,bid,data):
        db['ftods_blobs'][str(bid)] = data

    def deleteBlob(self,db,bid):
        bs = db['ftods_blobs']
        if bs.has_key(str(bid)):
            del db['ftods_blobs'][str(bid)]


    #############################
    #Operation Interface
    #############################
    def registerOperation(self,db,repoId,functionName,functionModule):
        od = db['ftods_operations']
        od[str(repoId)] = (functionName,functionModule)

    def getOperation(self,db,repoId):
        od = db['ftods_operations']
        return od.get(str(repoId))


    #Garbage Collection
    def collectGarbage(self):
        self._4ods_collectRepositoryObjectGarbage()



