#
# $Id$
#
# This file contains class definitions and interface to the database
# itself while defining objects that the database will return.
#

import rcs, os


#
# The most generic exception that this library will raise.
#
class PVVDBError(Exception):
    pass


#
# PVVMember is the class that represents a record in the database.
# Its interface is like a dict with a few additional methods to
# talk to the database itself.
#
class PVVMember(dict):

    def __init__(self, data, dbref):
        # Populate the object with the fields and values provided in the
        # `data' object.  `dbref' is a pointer to the PVVDB instance that
        # created the PVVMember instance.
        dict.__init__(self, data)
        self.dbref = dbref

    def save(self):
        # Synchronise the object to disk via the database interface.
        self.dbref.save(self)

    def load(self):
        # Update the object with the data on disk.
        o = self.dbref.load(data)
        self.update(o)

    def is_saved(self):
        # Checks whether the objects need to be saved or loaded.
        pass


#
# PVVDB is the class that interfaces to the directory structure and files.
# It also verifies the data read from the files and written to it.
#
class PVVDB:

    def __init__(self, base):
        # Find the record definition file, read it, verify it and
        # set the legal set of fields.
        self.base  = base               # base database directory
        self.locks = []                 # list of activated locks
        self.rcs   = rcs.RCS()          # an RCS interface function
        self.format = self.__format(
            os.path.join(self.base, ".format"))

    def __del__(self):
        # clean up locks and such before being deleted.
        for username in self.locks:
            self.rcs.unlock(username)

    def __filename(self, username):
        # transform the username into a tuple with the full name of
        # the databasefile and the latest revision number.
        return self.rcs.rcsname(os.path.join(self.base, username))

    def __format(self, file_path):
        # parses and verifies the .format files for a database.
        fh  = file(file_path)
        fmt = {}
        for line in fh:
            if line[0] == "#":
                continue
            name, value = line.strip().split(":", 1)
            value       = value.split("#", 1)[0].strip()
            if not value in ['scalar', 'list']:
                raise PVVDBError, "%s not legal .format datatype." % value
            fmt[name] = value
        return fmt

    def lock(self, username, pedantic=False):
        # Lock the record.
        if username in self.locks and pedantic:
            raise PVVDBError, "%s already locked." % `username`
        self.rcs.lock(self.__filename(username))
        self.locks.append(username)
        

    def unlock(self, username, pedantic=False):
        # Unlock the record.
        if not username in self.locks and pedantic:
            raise PVVDBError, "%s not locked." % `username`
        self.rcs.unlock(self.__filename(username))
        self.locks.remove(username)

    def load(self, username, lock=False):
        # Read the record from the database.  Will do an automatic lock
        # if `lock' is True.
        cwd = os.getcwd()
        os.chdir(self.base)
        rv = self.rcs.checkout(username + ",v")
        userfile = file(username)
        os.chdir(cwd)
        member = {}
        for line in userfile:
            key, value = line.strip().split(": ", 1)
            if not key in self.format:
                raise PVVDBError, "%s not a legal attribute." % `key`
            v = None
            # handle the list datatype
            if self.format[key] == 'list':
                v = []
                # XXX: needs to respect escaping of field separators
                for e in value.split(", "):
                    v.append(e)
            elif self.format[key] == 'scalar':
                v = value
            else:
                # we should not be here
                raise PVVDBError, "%s not legal datatype." % `self.format[key]`
            member[key] = v
        return PVVMember(member, self)

    def save(self, username, comment="", lock=False):
        # Write the record to the database.  Will automatically unlock
        # the record if `lock' is True.  A `comment' can be supplied that
        # explains the changes made.
        pass


#
# Regular interface to the database.
#

pathmap = {}

def connect(path):
    # returns a PVVDB instance to that path.  If asked for another
    # PVVDB instance with the same path as earlier, it will return
    # the same object.
    if not path in pathmap:
        pathmap[path] = PVVDB(path)
    return pathmap[path]

def disconnect(dbo):
    # do not do anything, really.  PVVDB objects will clean up
    # automatically when deleted.  is here for future database
    # systems.
    pass