aboutsummaryrefslogtreecommitdiff
path: root/cnotmuch/database.py
blob: 82c4f2bc2a86975ec09fc86748f2e523e56f87f1 (plain)
import ctypes
from ctypes import c_int, c_char_p, c_void_p
from cnotmuch.globals import nmlib, STATUS, NotmuchError


class Database(object):
    """ Wrapper around a notmuch_database_t

    Do note that as soon as we tear down this object, all derived queries,
    threads, and messages will be freed as well.
    """
    #constants
    MODE_READ_ONLY = 0
    MODE_READ_WRITE = 1

    _std_db_path = None
    """Class attribute of users default database"""

    """notmuch_database_get_path (notmuch_database_t *database)"""
    _get_path = nmlib.notmuch_database_get_path
    _get_path.restype = c_char_p

    """notmuch_database_open (const char *path, notmuch_database_mode_t mode)"""
    _open = nmlib.notmuch_database_open 
    _open.restype = c_void_p

    """ notmuch_database_find_message """
    _find_message = nmlib.notmuch_database_find_message
    _find_message.restype = c_void_p

    """notmuch_database_get_all_tags (notmuch_database_t *database)"""
    _get_all_tags = nmlib.notmuch_database_get_all_tags
    _get_all_tags.restype = c_void_p

    """ notmuch_database_create(const char *path):"""
    _create = nmlib.notmuch_database_create
    _create.restype = c_void_p

    def __init__(self, path=None, create=False, status= MODE_READ_ONLY):
        """ Open or create a notmuch database

        If path is None, we will try to read a users notmuch configuration and
        use his default database.
        Throws a NotmuchError in case of failure.
        """
        self._db = None
        if path is None:
            # no path specified. use a user's default database
            if Database._std_db_path is None:
                #the following line throws a NotmuchError if it fails
                Database._std_db_path = self._get_user_default_db()
            path = Database._std_db_path

        if create == False:
            self.open(path, status)
        else:
            self.create(path, status)

    def create(self, path, status=MODE_READ_ONLY):
        """ notmuch_database_create(const char *path)

        :returns: Raises :exc:`notmuch.NotmuchError` in case
                  of any failure (after printing an error message on stderr).
        """
        res = Database._create(path, status)

        if res is None:
            raise NotmuchError(
                message="Could not create the specified database")
        self._db = res

    def open(self, path, status= MODE_READ_ONLY): 
        """calls notmuch_database_open

        :returns: Raises :exc:`notmuch.NotmuchError` in case
                  of any failure (after printing an error message on stderr).
        """
        res = Database._open(path, status)

        if res is None:
            raise NotmuchError(
                message="Could not open the specified database")
        self._db = res

    def get_path(self):
        """notmuch_database_get_path (notmuch_database_t *database);  """
        return Database._get_path(self._db)

    def find_message(self, msgid):
        """notmuch_database_find_message
        :param msgid: The message id
        :ptype msgid: string

        :returns: Message() or None if no message is found or if an
                  out-of-memory situation occurs.
        """
        if self._db is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        msg_p = Database._find_message(self._db, msgid)
        if msg_p is None:
            return None
        return Message(msg_p, self)

    def get_all_tags(self):
        """Return a Tags() object (list of all tags found in the database)

        :returns: Tags() object or raises :exc:`NotmuchError` with 
                  STATUS.NULL_POINTER on error
        """
        if self._db is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        tags_p = Database._get_all_tags (self._db)
        if tags_p == None:
            raise NotmuchError(STATUS.NULL_POINTER)
        return Tags(tags_p, self)

    def __repr__(self):
        return "'Notmuch DB " + self.get_path() + "'"

    def __del__(self):
        """Close and free the notmuch database if needed"""
        if self._db is not None:
            print("Freeing the database now")
            nmlib.notmuch_database_close(self._db)

    def _get_user_default_db(self):
        """ Reads a user's notmuch config and returns his db location

        Throws a NotmuchError if it cannot find it"""
        from ConfigParser import SafeConfigParser
        import os.path
        config = SafeConfigParser()
        config.read(os.path.expanduser('~/.notmuch-config'))
        if not config.has_option('database','path'):
            raise NotmuchError(message=
                               "No DB path specified and no user default found")
        return config.get('database','path')

    @property
    def db_p(self):
        """Returns a pointer to the current notmuch_database_t or None"""
        return self._db

#------------------------------------------------------------------------------
class Query(object):
    """ Wrapper around a notmuch_query_t

    Do note that as soon as we tear down this object, all derived
    threads, and messages will be freed as well.
    """
    def __init__(self, db, querystr):
        pass

#------------------------------------------------------------------------------
class Tags(object):
    """Wrapper around notmuch_tags_t"""
    class notmuch_tags_t(ctypes.Structure):
        """the opaque tags struct that is returned by functions."""
        pass

    #notmuch_tags_get
    _get = nmlib.notmuch_tags_get
    _get.restype = c_char_p

    def __init__(self, tags_p, db=None):
        """
        msg_p is a pointer to an notmuch_message_t Structure. If it is None,
        we will raise an NotmuchError(STATUS.NULL_POINTER).

        Is passed the db these tags are derived from, and saves a
        reference to it, so we can automatically delete the db object
        once all derived objects are dead.

        Tags() provides an iterator over all contained tags. However, you will
        only be able to iterate over the Tags once, because the underlying C
        function only allows iterating once.
        #TODO: make the iterator work more than once and cache the tags in 
               the Python object.
        """
        if tags_p is None:
            NotmuchError(STATUS.NULL_POINTER)

        self._tags = tags_p
        self._db = db
        print "Inited Tags derived from %s" %(str(db))
    
    def __iter__(self):
        """ Make Tags an iterator """
        return self

    def next(self):
        if self._tags is None:
            raise StopIteration
        nmlib.notmuch_tags_move_to_next(self._tags)
        if not nmlib.notmuch_tags_valid(self._tags):
            print("Freeing the Tags now")
            nmlib.notmuch_tags_destroy (self._tags)
            raise StopIteration
        return Tags._get (self._tags)

    def __del__(self):
        """Close and free the notmuch tags"""
        if self._tags is not None:
            print("Freeing the Tags now")
            nmlib.notmuch_tags_destroy (self._tags)

#------------------------------------------------------------------------------
class Message(object):
    """Wrapper around notmuch_message_t"""

    """notmuch_message_get_filename (notmuch_message_t *message)"""
    _get_filename = nmlib.notmuch_message_get_filename
    _get_filename.restype = c_char_p 
    """notmuch_message_get_message_id (notmuch_message_t *message)"""
    _get_message_id = nmlib.notmuch_message_get_message_id
    _get_message_id.restype = c_char_p 

    """notmuch_message_get_tags (notmuch_message_t *message)"""
    _get_tags = nmlib.notmuch_message_get_tags
    _get_tags.restype = c_void_p

    def __init__(self, msg_p, parent=None):
        """
        msg_p is a pointer to an notmuch_message_t Structure. If it is None,
        we will raise an NotmuchError(STATUS.NULL_POINTER).

        Is a 'parent' object is passed which this message is derived from,
        we save a reference to it, so we can automatically delete the parent
        object once all derived objects are dead.
        """
        if msg_p is None:
            NotmuchError(STATUS.NULL_POINTER)
        self._msg = msg_p
        self._parent = parent
        print "Inited Message derived from %s" %(str(parent))


    def get_message_id(self):
        """ return the msg id
        
        Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return Message._get_message_id(self._msg)


    def get_filename(self):
        """ return the msg filename
        
        Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return Message._get_filename(self._msg)

    def get_tags(self):
        """ return the msg tags
        
        Raises NotmuchError(STATUS.NOT_INITIALIZED) if not inited
        Raises NotmuchError(STATUS.NULL_POINTER) on error.
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        tags_p = Message._get_tags(self._msg)
        if tags_p == None:
            raise NotmuchError(STATUS.NULL_POINTER)
        return Tags(tags_p, self)

    def __del__(self):
        """Close and free the notmuch Message"""
        if self._msg is not None:
            print("Freeing the Message now")
            nmlib.notmuch_message_destroy (self._msg)