########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Rdf/Drivers/Dbm.py,v 1.18 2005/03/02 03:59:06 mbrown Exp $
"""
A persistent RDF model driver using the Memory driver, but pickling to the file system

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import os, cPickle, threading
from Ft.Rdf.Drivers import Memory
from Ft.Rdf.Drivers import PROPERTIES

def InitializeModule():
    """
    Post-import hook to initialize module's runtime variables that are not
    required at import time, but will be needed before the module-level
    functions are called.
    """
    global DATABASE_DIR

    from Ft import GetConfigVar
    dbdir = os.path.join(GetConfigVar('LOCALSTATEDIR'), 'Dbm')
    dbdir = os.environ.get('FT_DATABASE_DIR', dbdir)

    # Normalize path, remove unnecessary slashes
    DATABASE_DIR = os.path.abspath(dbdir)

    # FIXME: l10n
    if not os.path.isdir(DATABASE_DIR):
        raise ValueError("Dbm database directory %s does not exist;"
                         " create it or check FT_DATABASE_DIR" % DATABASE_DIR)
    return


def CreateFileName(dbName):
    if dbName[:4] != 'rdf:':
        return os.path.join(DATABASE_DIR, 'ftrdf__%s' % dbName)
    return dbName[4:]

# Management functions
def ExistsDb(dbName, modelName='default'):
    fName = CreateFileName(dbName)
    if os.path.exists(fName):
        try:
            fd = open(fName)
            d = cPickle.load(fd)
            fd.close()
            return 'ftrdf_%s_statements' % modelName in d and 'ftrdf_%s_bound' % modelName in d
        except:
            return 0
    return 0


def CreateDb(dbName, modelName='default'):
    fName = CreateFileName(dbName)
    if os.path.exists(fName):
        os.unlink(fName)
    d = {}
    d['ftrdf_%s_statements' % modelName] = []
    d['ftrdf_%s_bound' % modelName] = {}
    fd = open(fName, 'wb')
    cPickle.dump(d, fd)
    fd.close()
    return GetDb(dbName)

def GetDb(dbName, modelName='default'):
    return DbAdapter(dbName, modelName)

def DestroyDb(dbName, modelName='default'):
    fName = CreateFileName(dbName)
    if os.path.exists(fName):
        os.unlink(fName)

# The RDF Adapter interface
class DbAdapter(Memory.DbAdapter):
    def __init__(self, name, modelName='default'):
        self._name = 'ft__%s' % name
        self._fName = CreateFileName(name)
        self._changed = False
        self._modelName = modelName
        self.props = {PROPERTIES.OBJECT_TYPE_SUPPORTED: True}
        return

    def begin(self):
        self._statements, self._bound = g_modelCache.begin(self._fName, self._modelName)
        self._db = True
        return

    def commit(self):
        if self._changed:
            g_modelCache.commit(self._fName, self._statements, self._bound,
                                self._modelName)
        self._db = False
        return

    def rollback(self):
        self._db = False
        return


    def add(self, statements):
        self._changed = True
        return Memory.DbAdapter.add(self, statements)


    def remove(self, statements):
        self._changed = True
        return Memory.DbAdapter.remove(self, statements)

    def removePattern(self, subject, predicate, object, statementUri,
                      scope, flags):
        self._changed = True
        return Memory.DbAdapter.removePattern(self, subject, predicate,
                                              object, statementUri, scope,
                                              flags)

    def bind(self, object, name, scope):
        self._changed = True
        return Memory.DbAdapter.bind(self, object, name, scope)

    def unbind(self, name, scope):
        self._changed = True
        return Memory.DbAdapter.unbind(self,name, scope)


class ModelCache:

    def __init__(self):
        self._locks = {}

    def begin(self, fName, modelName):

        if fName not in self._locks:
            self._locks[fName] = threading.RLock()

        self._locks[fName].acquire()
        try:
            fd = open(fName, 'rb')
            db = cPickle.load(fd)
            fd.close()
            stmts = {modelName:db['ftrdf_%s_statements' % modelName]}
            return (stmts, db['ftrdf_%s_bound' % modelName])
        finally:
            self._locks[fName].release()


    def commit(self, fName, statements, bound, modelName):

        if fName not in self._locks:
            self._locks[fName] = threading.RLock()

        self._locks[fName].acquire()
        try:
            d = {'ftrdf_%s_statements' % modelName:statements[modelName],
                 'ftrdf_%s_bound' % modelName:bound}
            fd = open(fName, 'wb')
            cPickle.dump(d, fd)
            fd.close()
        finally:
            self._locks[fName].release()


g_modelCache = ModelCache()

