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
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)
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')
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)
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)
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)
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
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)
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)
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)
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
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)
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
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
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)
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)
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)
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
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)
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
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
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')
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)
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')
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()
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)
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)
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')
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
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')