Beispiel #1
0
    def create(self, path):
        """Creates a new notmuch database

        This function is used by __init__() and usually does not need
        to be called directly. It wraps the underlying
        *notmuch_database_create* function and creates a new notmuch
        database at *path*. It will always return a database in :attr:`MODE`
        .READ_WRITE mode as creating an empty database for
        reading only does not make a great deal of sense.

        :param path: A directory in which we should create the database.
        :type path: str
        :returns: Nothing
        :exception: :exc:`NotmuchError` in case of any failure
                    (possibly after printing an error message on stderr).
        """
        if self._db is not None:
            raise NotmuchError(message="Cannot create db, this Database() "
                               "already has an open one.")

        res = Database._create(_str(path), Database.MODE.READ_WRITE)

        if not res:
            raise NotmuchError(
                message="Could not create the specified database")
        self._db = res
Beispiel #2
0
    def get_tags(self):
        """ Returns the message tags

        In the Notmuch database, tags are stored on individual
        messages, not on threads. So the tags returned here will be all
        tags of the messages which matched the search and which belong to
        this thread.

        The :class:`Tags` object is owned by the thread and as such, will only
        be valid for as long as this :class:`Thread` is valid (e.g. until the
        query from which it derived is explicitely deleted).

        :returns: A :class:`Tags` iterator.
        :exception: :exc:`NotmuchError`

                      * STATUS.NOT_INITIALIZED if the thread
                        is not initialized.
                      * STATUS.NULL_POINTER, on error
        """
        if self._thread is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        tags_p = Thread._get_tags(self._thread)
        if tags_p == None:
            raise NotmuchError(STATUS.NULL_POINTER)
        return Tags(tags_p, self)
Beispiel #3
0
    def get_header(self, header):
        """Get the value of the specified header.

        The value will be read from the actual message file, not from
        the notmuch database. The header name is case insensitive.

        Returns an empty string ("") if the message does not contain a
        header line matching 'header'.

        :param header: The name of the header to be retrieved.
                       It is not case-sensitive.
        :type header: str
        :returns: The header value as string
        :exception: :exc:`NotmuchError`

                    * STATUS.NOT_INITIALIZED if the message
                      is not initialized.
                    * STATUS.NULL_POINTER if any error occured.
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        #Returns NULL if any error occurs.
        header = Message._get_header(self._msg, header)
        if header == None:
            raise NotmuchError(STATUS.NULL_POINTER)
        return header.decode('UTF-8')
Beispiel #4
0
    def thaw(self):
        """Thaws the current 'message'

        Thaw the current 'message', synchronizing any changes that may have
        occurred while 'message' was frozen into the notmuch database.

        See :meth:`freeze` for an example of how to use this
        function to safely provide tag changes.

        Multiple calls to freeze/thaw are valid and these calls with
        "stack". That is there must be as many calls to thaw as to freeze
        before a message is actually thawed.

        :returns: STATUS.SUCCESS if the message was successfully frozen.
                  Raises an exception otherwise.
        :exception: :exc:`NotmuchError`. They have the following meaning:

                   STATUS.UNBALANCED_FREEZE_THAW
                     An attempt was made to thaw an unfrozen message.
                     That is, there have been an unbalanced number of calls
                     to :meth:`freeze` and :meth:`thaw`.
                   STATUS.NOT_INITIALIZED
                     The message has not been initialized.
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        status = nmlib.notmuch_message_thaw(self._msg)

        if STATUS.SUCCESS == status:
            # return on success
            return status

        raise NotmuchError(status)
Beispiel #5
0
    def get_toplevel_messages(self):
        """Returns a :class:`Messages` iterator for the top-level messages in
           'thread'

           This iterator will not necessarily iterate over all of the messages
           in the thread. It will only iterate over the messages in the thread
           which are not replies to other messages in the thread.

           To iterate over all messages in the thread, the caller will need to
           iterate over the result of :meth:`Message.get_replies` for each
           top-level message (and do that recursively for the resulting
           messages, etc.).

        :returns: :class:`Messages`
        :exception: :exc:`NotmuchError`

                      * STATUS.NOT_INITIALIZED if query is not inited
                      * STATUS.NULL_POINTER if search_messages failed
        """
        if self._thread is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        msgs_p = Thread._get_toplevel_messages(self._thread)

        if not msgs_p:
            raise NotmuchError(STATUS.NULL_POINTER)

        return Messages(msgs_p, self)
Beispiel #6
0
    def freeze(self):
        """Freezes the current state of 'message' within the database

        This means that changes to the message state, (via :meth:`add_tag`,
        :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
        committed to the database until the message is :meth:`thaw`ed.

        Multiple calls to freeze/thaw are valid and these calls will
        "stack". That is there must be as many calls to thaw as to freeze
        before a message is actually thawed.

        The ability to do freeze/thaw allows for safe transactions to
        change tag values. For example, explicitly setting a message to
        have a given set of tags might look like this::

          msg.freeze()
          msg.remove_all_tags(False)
          for tag in new_tags:
              msg.add_tag(tag, False)
          msg.thaw()
          msg.tags_to_maildir_flags()

        With freeze/thaw used like this, the message in the database is
        guaranteed to have either the full set of original tag values, or
        the full set of new tag values, but nothing in between.

        Imagine the example above without freeze/thaw and the operation
        somehow getting interrupted. This could result in the message being
        left with no tags if the interruption happened after
        :meth:`remove_all_tags` but before :meth:`add_tag`.

        :returns: STATUS.SUCCESS if the message was successfully frozen.
                  Raises an exception otherwise.
        :exception: :exc:`NotmuchError`. They have the following meaning:

                   STATUS.READ_ONLY_DATABASE
                     Database was opened in read-only mode so message cannot
                     be modified.
                   STATUS.NOT_INITIALIZED
                     The message has not been initialized.
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        status = nmlib.notmuch_message_freeze(self._msg)

        if STATUS.SUCCESS == status:
            # return on success
            return status

        raise NotmuchError(status)
Beispiel #7
0
    def find_message_by_filename(self, filename):
        """Find a message with the given filename

        .. warning::

            This call needs a writeable database in
            :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
            exit the program if this method is used on a read-only database!

        :returns: If the database contains a message with the given
            filename, then a class:`Message:` is returned.  This
            function returns None if no message is found with the given
            filename.

        :exception:
            :exc:`OutOfMemoryError`
                  If an Out-of-memory occured while constructing the message.
            :exc:`XapianError`
                  In case of a Xapian Exception. These exceptions
                  include "Database modified" situations, e.g. when the
                  notmuch database has been modified by another program
                  in the meantime. In this case, you should close and
                  reopen the database and retry.
            :exc:`NotInitializedError` if
                    the database was not intitialized.

        *Added in notmuch 0.9*"""
        self._assert_db_is_initialized()
        msg_p = NotmuchMessageP()
        status = Database._find_message_by_filename(self._db, _str(filename),
                                                    byref(msg_p))
        if status != STATUS.SUCCESS:
            raise NotmuchError(status)
        return msg_p and Message(msg_p, self) or None
Beispiel #8
0
    def get_directory(self, path):
        """Returns a :class:`Directory` of path,
        (creating it if it does not exist(?))

        .. warning:: This call needs a writeable database in
           :attr:`Database.MODE`.READ_WRITE mode. The underlying library will exit the
           program if this method is used on a read-only database!

        :param path: An unicode string containing the path relative to the path
              of database (see :meth:`get_path`), or else should be an absolute path
              with initial components that match the path of 'database'.
        :returns: :class:`Directory` or raises an exception.
        :exception:
            :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR
                    If path is not relative database or absolute with initial
                    components same as database.
        """
        self._assert_db_is_initialized()
        # sanity checking if path is valid, and make path absolute
        if path[0] == os.sep:
            # we got an absolute path
            if not path.startswith(self.get_path()):
                # but its initial components are not equal to the db path
                raise NotmuchError(STATUS.FILE_ERROR,
                                   message="Database().get_directory() called "
                                           "with a wrong absolute path.")
            abs_dirpath = path
        else:
            #we got a relative path, make it absolute
            abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))

        dir_p = Database._get_directory(self._db, _str(path))

        # return the Directory, init it with the absolute path
        return Directory(_str(abs_dirpath), dir_p, self)
Beispiel #9
0
    def get_replies(self):
        """Gets all direct replies to this message as :class:`Messages`
        iterator

        .. note:: This call only makes sense if 'message' was
          ultimately obtained from a :class:`Thread` object, (such as
          by coming directly from the result of calling
          :meth:`Thread.get_toplevel_messages` or by any number of
          subsequent calls to :meth:`get_replies`). If this message was
          obtained through some non-thread means, (such as by a call
          to :meth:`Query.search_messages`), then this function will
          return `None`.

        :returns: :class:`Messages` or `None` if there are no replies to
            this message.
        :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
                    is not initialized.
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        msgs_p = Message._get_replies(self._msg)

        if msgs_p is None:
            return None

        return Messages(msgs_p, self)
Beispiel #10
0
    def maildir_flags_to_tags(self):
        """Synchronize file Maildir flags to notmuch tags

            Flag    Action if present
            ----    -----------------
            'D'     Adds the "draft" tag to the message
            'F'     Adds the "flagged" tag to the message
            'P'     Adds the "passed" tag to the message
            'R'     Adds the "replied" tag to the message
            'S'     Removes the "unread" tag from the message

        For each flag that is not present, the opposite action
        (add/remove) is performed for the corresponding tags.  If there
        are multiple filenames associated with this message, the flag is
        considered present if it appears in one or more filenames. (That
        is, the flags from the multiple filenames are combined with the
        logical OR operator.)

        As a convenience, you can set the sync_maildir_flags parameter in
        :meth:`Database.add_message` to implicitly call this.

        :returns: a :class:`STATUS`. In short, you want to see
            notmuch.STATUS.SUCCESS here. See there for details."""
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        status = Message._tags_to_maildir_flags(self._msg)
Beispiel #11
0
    def find_message(self, msgid):
        """Returns a :class:`Message` as identified by its message ID

        Wraps the underlying *notmuch_database_find_message* function.

        :param msgid: The message ID
        :type msgid: unicode or str
        :returns: :class:`Message` or `None` if no message is found.
        :exception:
            :exc:`OutOfMemoryError`
                  If an Out-of-memory occured while constructing the message.
            :exc:`XapianError`
                  In case of a Xapian Exception. These exceptions
                  include "Database modified" situations, e.g. when the
                  notmuch database has been modified by another program
                  in the meantime. In this case, you should close and
                  reopen the database and retry.
            :exc:`NotInitializedError` if
                    the database was not intitialized.
        """
        self._assert_db_is_initialized()
        msg_p = NotmuchMessageP()
        status = Database._find_message(self._db, _str(msgid), byref(msg_p))
        if status != STATUS.SUCCESS:
            raise NotmuchError(status)
        return msg_p and Message(msg_p, self) or None
Beispiel #12
0
    def get_tags(self):
        """Returns the message tags

        :returns: A :class:`Tags` iterator.
        :exception: :exc:`NotmuchError`

                      * STATUS.NOT_INITIALIZED if the message
                        is not initialized.
                      * 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)
Beispiel #13
0
 def next(self):
     if self._tags is None:
         raise NotmuchError(STATUS.NOT_INITIALIZED)
     if not nmlib.notmuch_tags_valid(self._tags):
         self._tags = None
         raise StopIteration
     tag = Tags._get(self._tags).decode('UTF-8')
     nmlib.notmuch_tags_move_to_next(self._tags)
     return tag
Beispiel #14
0
    def add_tag(self, tag, sync_maildir_flags=False):
        """Adds a tag to the given message

        Adds a tag to the current message. The maximal tag length is defined in
        the notmuch library and is currently 200 bytes.

        :param tag: String with a 'tag' to be added.

        :param sync_maildir_flags: If notmuch configuration is set to do
            this, add maildir flags corresponding to notmuch tags. See
            underlying method :meth:`tags_to_maildir_flags`. Use False
            if you want to add/remove many tags on a message without
            having to physically rename the file every time. Do note,
            that this will do nothing when a message is frozen, as tag
            changes will not be committed to the database yet.

        :returns: STATUS.SUCCESS if the tag was successfully added.
                  Raises an exception otherwise.
        :exception: :exc:`NotmuchError`. They have the following meaning:

                  STATUS.NULL_POINTER
                    The 'tag' argument is NULL
                  STATUS.TAG_TOO_LONG
                    The length of 'tag' is too long
                    (exceeds Message.NOTMUCH_TAG_MAX)
                  STATUS.READ_ONLY_DATABASE
                    Database was opened in read-only mode so message cannot be
                    modified.
                  STATUS.NOT_INITIALIZED
                     The message has not been initialized.
       """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        status = nmlib.notmuch_message_add_tag(self._msg, _str(tag))

        # bail out on failure
        if status != STATUS.SUCCESS:
            raise NotmuchError(status)

        if sync_maildir_flags:
            self.tags_to_maildir_flags()
        return STATUS.SUCCESS
Beispiel #15
0
    def get_message_id(self):
        """Returns the message ID

        :returns: String with a message ID
        :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
                    is not initialized.
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return Message._get_message_id(self._msg)
Beispiel #16
0
    def get_filename(self):
        """Returns the file path of the message file

        :returns: Absolute file path & name of the message file
        :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
              is not initialized.
        """
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return Message._get_filename(self._msg)
Beispiel #17
0
    def collect_tags(self):
        """Return the unique :class:`Tags` in the contained messages

        :returns: :class:`Tags`
        :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited

        .. note:: :meth:`collect_tags` will iterate over the messages and
          therefore will not allow further iterations.
        """
        if self._msgs is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        # collect all tags (returns NULL on error)
        tags_p = Messages._collect_tags(self._msgs)
        #reset _msgs as we iterated over it and can do so only once
        self._msgs = None

        if tags_p == None:
            raise NotmuchError(STATUS.NULL_POINTER)
        return Tags(tags_p, self)
Beispiel #18
0
    def __next__(self):
        if self._threads is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        if not self._valid(self._threads):
            self._threads = None
            raise StopIteration

        thread = Thread(Threads._get(self._threads), self)
        self._move_to_next(self._threads)
        return thread
Beispiel #19
0
    def get_oldest_date(self):
        """Returns time_t of the oldest message date

        :returns: A time_t timestamp.
        :rtype: c_unit64
        :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
                    is not initialized.
        """
        if self._thread is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return Thread._get_oldest_date(self._thread)
Beispiel #20
0
    def next(self):
        if self._msgs is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        if not nmlib.notmuch_messages_valid(self._msgs):
            self._msgs = None
            raise StopIteration

        msg = Message(Messages._get(self._msgs), self)
        nmlib.notmuch_messages_move_to_next(self._msgs)
        return msg
Beispiel #21
0
    def next(self):
        if self._files_p is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        if not nmlib.notmuch_filenames_valid(self._files_p):
            self._files_p = None
            raise StopIteration

        file = Filenames._get(self._files_p)
        nmlib.notmuch_filenames_move_to_next(self._files_p)
        return file
Beispiel #22
0
    def __next__(self):
        if not self._files_p:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        if not self._valid(self._files_p):
            self._files_p = None
            raise StopIteration

        file_ = Filenames._get(self._files_p)
        self._move_to_next(self._files_p)
        return file_.decode('utf-8', 'ignore')
Beispiel #23
0
    def get_all_tags(self):
        """Returns :class:`Tags` with a list of all tags found in the database

        :returns: :class:`Tags`
        :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER on error
        """
        self._assert_db_is_initialized()
        tags_p = Database._get_all_tags(self._db)
        if tags_p == None:
            raise NotmuchError(STATUS.NULL_POINTER)
        return Tags(tags_p, self)
Beispiel #24
0
    def get_subject(self):
        """Returns the Subject of 'thread'

        The returned string belongs to 'thread' and will only be valid for
        as long as this Thread() is not deleted.
        """
        if self._thread is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        subject = Thread._get_subject(self._thread)
        if subject is None:
            return None
        return subject.decode('UTF-8', 'ignore')
Beispiel #25
0
    def get_filenames(self):
        """Get all filenames for the email corresponding to 'message'

        Returns a Filenames() generator with all absolute filepaths for
        messages recorded to have the same Message-ID. These files must
        not necessarily have identical content."""
        if self._msg is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        files_p = Message._get_filenames(self._msg)

        return Filenames(files_p, self).as_generator()
Beispiel #26
0
    def get_matched_messages(self):
        """Returns the number of messages in 'thread' that matched the query

        :returns: The number of all messages belonging to this thread that
                  matched the :class:`Query`from which this thread was created.
                  Contrast with :meth:`get_total_messages`.
        :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread
                    is not initialized.
        """
        if self._thread is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return self._get_matched_messages(self._thread)
Beispiel #27
0
    def get_total_messages(self):
        """Get the total number of messages in 'thread'

        :returns: The number of all messages in the database
                  belonging to this thread. Contrast with
                  :meth:`get_matched_messages`.
        :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread
                    is not initialized.
        """
        if self._thread is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return self._get_total_messages(self._thread)
Beispiel #28
0
    def get_thread_id(self):
        """Get the thread ID of 'thread'

        The returned string belongs to 'thread' and will only be valid
        for as long as the thread is valid.

        :returns: String with a message ID
        :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the thread
                    is not initialized.
        """
        if self._thread is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)
        return Thread._get_thread_id(self._thread).decode('utf-8', 'ignore')
Beispiel #29
0
    def as_generator(self):
        """Return generator of Filenames

        This is the main function that will usually be used by the
        user."""
        if self._files is None:
            raise NotmuchError(STATUS.NOT_INITIALIZED)

        while nmlib.notmuch_filenames_valid(self._files):
            yield Filenames._get(self._files)
            nmlib.notmuch_filenames_move_to_next(self._files)

        self._files = None
Beispiel #30
0
    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
        config = SafeConfigParser()
        conf_f = os.getenv('NOTMUCH_CONFIG',
                           os.path.expanduser('~/.notmuch-config'))
        config.read(conf_f)
        if not config.has_option('database', 'path'):
            raise NotmuchError(message="No DB path specified"
                                       " and no user default found")
        return config.get('database', 'path').decode('utf-8')