2 from ctypes import c_int, c_char_p, c_void_p, c_uint, c_uint64, c_bool, byref
3 from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
5 from datetime import date
7 class Database(object):
8 """Represents a notmuch database (wraps notmuch_database_t)
10 .. note:: Do remember that as soon as we tear down this object,
11 all underlying derived objects such as queries, threads,
12 messages, tags etc will be freed by the underlying library
13 as well. Accessing these objects will lead to segfaults and
14 other unexpected behavior. See above for more details.
17 """Class attribute to cache user's default database"""
19 MODE = Enum(['READ_ONLY','READ_WRITE'])
20 """Constants: Mode in which to open the database"""
22 """notmuch_database_get_path (notmuch_database_t *database)"""
23 _get_path = nmlib.notmuch_database_get_path
24 _get_path.restype = c_char_p
26 """notmuch_database_get_version"""
27 _get_version = nmlib.notmuch_database_get_version
28 _get_version.restype = c_uint
30 """notmuch_database_open (const char *path, notmuch_database_mode_t mode)"""
31 _open = nmlib.notmuch_database_open
32 _open.restype = c_void_p
34 """ notmuch_database_find_message """
35 _find_message = nmlib.notmuch_database_find_message
36 _find_message.restype = c_void_p
38 """notmuch_database_get_all_tags (notmuch_database_t *database)"""
39 _get_all_tags = nmlib.notmuch_database_get_all_tags
40 _get_all_tags.restype = c_void_p
42 """ notmuch_database_create(const char *path):"""
43 _create = nmlib.notmuch_database_create
44 _create.restype = c_void_p
46 def __init__(self, path=None, create=False, mode= 0):
47 """If *path* is *None*, we will try to read a users notmuch
48 configuration and use his configured database. The location of the
49 configuration file can be specified through the environment variable
50 *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
52 If *create* is `True`, the database will always be created in
53 :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
55 :param path: Directory to open/create the database in (see
56 above for behavior if `None`)
57 :type path: `str` or `None`
58 :param create: Pass `False` to open an existing, `True` to create a new
61 :param mode: Mode to open a database in. Is always
62 :attr:`MODE`.READ_WRITE when creating a new one.
63 :type mode: :attr:`MODE`
65 :exception: :exc:`NotmuchError` in case of failure.
69 # no path specified. use a user's default database
70 if Database._std_db_path is None:
71 #the following line throws a NotmuchError if it fails
72 Database._std_db_path = self._get_user_default_db()
73 path = Database._std_db_path
80 def _verify_initialized_db(self):
81 """Raises a NotmuchError in case self._db is still None"""
83 raise NotmuchError(STATUS.NOT_INITIALIZED)
85 def create(self, path):
86 """Creates a new notmuch database
88 This function is used by __init__() and usually does not need
89 to be called directly. It wraps the underlying
90 *notmuch_database_create* function and creates a new notmuch
91 database at *path*. It will always return a database in
92 :attr:`MODE`.READ_WRITE mode as creating an empty database for
93 reading only does not make a great deal of sense.
95 :param path: A directory in which we should create the database.
98 :exception: :exc:`NotmuchError` in case of any failure
99 (after printing an error message on stderr).
101 if self._db is not None:
103 message="Cannot create db, this Database() already has an open one.")
105 res = Database._create(path, Database.MODE.READ_WRITE)
109 message="Could not create the specified database")
112 def open(self, path, mode= 0):
113 """Opens an existing database
115 This function is used by __init__() and usually does not need
116 to be called directly. It wraps the underlying
117 *notmuch_database_open* function.
119 :param status: Open the database in read-only or read-write mode
120 :type status: :attr:`MODE`
122 :exception: Raises :exc:`NotmuchError` in case
123 of any failure (after printing an error message on stderr).
126 res = Database._open(path, mode)
130 message="Could not open the specified database")
134 """Returns the file path of an open database
136 Wraps notmuch_database_get_path"""
137 # Raise a NotmuchError if not initialized
138 self._verify_initialized_db()
140 return Database._get_path(self._db)
142 def get_version(self):
143 """Returns the database format version
145 :returns: The database version as positive integer
146 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
147 the database was not intitialized.
149 # Raise a NotmuchError if not initialized
150 self._verify_initialized_db()
152 return Database._get_version (self._db)
154 def needs_upgrade(self):
155 """Does this database need to be upgraded before writing to it?
157 If this function returns True then no functions that modify the
158 database (:meth:`add_message`, :meth:`add_tag`,
159 :meth:`Directory.set_mtime`, etc.) will work unless :meth:`upgrade`
160 is called successfully first.
162 :returns: `True` or `False`
163 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
164 the database was not intitialized.
166 # Raise a NotmuchError if not initialized
167 self._verify_initialized_db()
169 return notmuch_database_needs_upgrade(self.db)
171 def add_message(self, filename):
172 """Adds a new message to the database
174 `filename` should be a path relative to the path of the open
175 database (see :meth:`get_path`), or else should be an absolute
176 filename with initial components that match the path of the
179 The file should be a single mail message (not a multi-message mbox)
180 that is expected to remain at its current location, since the
181 notmuch database will reference the filename, and will not copy the
182 entire contents of the file.
184 :returns: On success, we return
186 1) a :class:`Message` object that can be used for things
187 such as adding tags to the just-added message.
188 2) one of the following STATUS values:
191 Message successfully added to database.
192 STATUS.DUPLICATE_MESSAGE_ID
193 Message has the same message ID as another message already
194 in the database. The new filename was successfully added
195 to the message in the database.
197 :rtype: 2-tuple(:class:`Message`, STATUS)
199 :exception: Raises a :exc:`NotmuchError` with the following meaning.
200 If such an exception occurs, nothing was added to the database.
203 An error occurred trying to open the file, (such as
204 permission denied, or file not found, etc.).
205 STATUS.FILE_NOT_EMAIL
206 The contents of filename don't look like an email message.
207 STATUS.READ_ONLY_DATABASE
208 Database was opened in read-only mode so no message can
210 STATUS.NOT_INITIALIZED
211 The database has not been initialized.
213 # Raise a NotmuchError if not initialized
214 self._verify_initialized_db()
217 status = nmlib.notmuch_database_add_message(self._db,
221 if not status in [STATUS.SUCCESS,STATUS.DUPLICATE_MESSAGE_ID]:
222 raise NotmuchError(status)
224 #construct Message() and return
225 msg = Message(msg_p, self)
228 def remove_message(self, filename):
229 """Removes a message from the given notmuch database
231 Note that only this particular filename association is removed from
232 the database. If the same message (as determined by the message ID)
233 is still available via other filenames, then the message will
234 persist in the database for those filenames. When the last filename
235 is removed for a particular message, the database content for that
236 message will be entirely removed.
238 :returns: A STATUS.* value with the following meaning:
241 The last filename was removed and the message was removed
243 STATUS.DUPLICATE_MESSAGE_ID
244 This filename was removed but the message persists in the
245 database with at least one other filename.
247 :exception: Raises a :exc:`NotmuchError` with the following meaning.
248 If such an exception occurs, nothing was removed from the database.
250 STATUS.READ_ONLY_DATABASE
251 Database was opened in read-only mode so no message can be
253 STATUS.NOT_INITIALIZED
254 The database has not been initialized.
256 # Raise a NotmuchError if not initialized
257 self._verify_initialized_db()
259 status = nmlib.notmuch_database_remove_message(self._db,
262 def find_message(self, msgid):
263 """Returns a :class:`Message` as identified by its message ID
265 Wraps the underlying *notmuch_database_find_message* function.
267 :param msgid: The message ID
269 :returns: :class:`Message` or `None` if no message is found or if an
270 out-of-memory situation occurs.
271 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
272 the database was not intitialized.
274 # Raise a NotmuchError if not initialized
275 self._verify_initialized_db()
277 msg_p = Database._find_message(self._db, msgid)
280 return Message(msg_p, self)
282 def get_all_tags(self):
283 """Returns :class:`Tags` with a list of all tags found in the database
285 :returns: :class:`Tags`
286 :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
288 # Raise a NotmuchError if not initialized
289 self._verify_initialized_db()
291 tags_p = Database._get_all_tags (self._db)
293 raise NotmuchError(STATUS.NULL_POINTER)
294 return Tags(tags_p, self)
296 def create_query(self, querystring):
297 """Returns a :class:`Query` derived from this database
299 This is a shorthand method for doing::
302 # Automatically frees the Database() when 'q' is deleted
304 q = Database(dbpath).create_query('from:"Biene Maja"')
306 # long version, which is functionally equivalent but will keep the
307 # Database in the 'db' variable around after we delete 'q':
309 db = Database(dbpath)
310 q = Query(db,'from:"Biene Maja"')
312 This function is a python extension and not in the underlying C API.
314 # Raise a NotmuchError if not initialized
315 self._verify_initialized_db()
317 return Query(self, querystring)
320 return "'Notmuch DB " + self.get_path() + "'"
323 """Close and free the notmuch database if needed"""
324 if self._db is not None:
325 logging.debug("Freeing the database now")
326 nmlib.notmuch_database_close(self._db)
328 def _get_user_default_db(self):
329 """ Reads a user's notmuch config and returns his db location
331 Throws a NotmuchError if it cannot find it"""
332 from ConfigParser import SafeConfigParser
333 config = SafeConfigParser()
334 conf_f = os.getenv('NOTMUCH_CONFIG',
335 os.path.expanduser('~/.notmuch-config'))
337 if not config.has_option('database','path'):
338 raise NotmuchError(message=
339 "No DB path specified and no user default found")
340 return config.get('database','path')
344 """Property returning a pointer to `notmuch_database_t` or `None`
346 This should normally not be needed by a user (and is not yet
347 guaranteed to remain stable in future versions).
351 #------------------------------------------------------------------------------
353 """Represents a search query on an opened :class:`Database`.
355 A query selects and filters a subset of messages from the notmuch
356 database we derive from.
358 Technically, it wraps the underlying *notmuch_query_t* struct.
360 .. note:: Do remember that as soon as we tear down this object,
361 all underlying derived objects such as threads,
362 messages, tags etc will be freed by the underlying library
363 as well. Accessing these objects will lead to segfaults and
364 other unexpected behavior. See above for more details.
367 SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
368 """Constants: Sort order in which to return results"""
370 """notmuch_query_create"""
371 _create = nmlib.notmuch_query_create
372 _create.restype = c_void_p
374 """notmuch_query_search_messages"""
375 _search_messages = nmlib.notmuch_query_search_messages
376 _search_messages.restype = c_void_p
379 """notmuch_query_count_messages"""
380 _count_messages = nmlib.notmuch_query_count_messages
381 _count_messages.restype = c_uint
383 def __init__(self, db, querystr):
385 :param db: An open database which we derive the Query from.
386 :type db: :class:`Database`
387 :param querystr: The query string for the message.
392 self.create(db, querystr)
394 def create(self, db, querystr):
395 """Creates a new query derived from a Database
397 This function is utilized by __init__() and usually does not need to
400 :param db: Database to create the query from.
401 :type db: :class:`Database`
402 :param querystr: The query string
405 :exception: :exc:`NotmuchError`
407 * STATUS.NOT_INITIALIZED if db is not inited
408 * STATUS.NULL_POINTER if the query creation failed
412 raise NotmuchError(STATUS.NOT_INITIALIZED)
413 # create reference to parent db to keep it alive
416 # create query, return None if too little mem available
417 query_p = Query._create(db.db_p, querystr)
419 NotmuchError(STATUS.NULL_POINTER)
420 self._query = query_p
422 def set_sort(self, sort):
423 """Set the sort order future results will be delivered in
425 Wraps the underlying *notmuch_query_set_sort* function.
427 :param sort: Sort order (see :attr:`Query.SORT`)
429 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not
432 if self._query is None:
433 raise NotmuchError(STATUS.NOT_INITIALIZED)
435 nmlib.notmuch_query_set_sort(self._query, sort)
437 def search_messages(self):
438 """Filter messages according to the query and return
439 :class:`Messages` in the defined sort order
441 Technically, it wraps the underlying
442 *notmuch_query_search_messages* function.
444 :returns: :class:`Messages`
445 :exception: :exc:`NotmuchError`
447 * STATUS.NOT_INITIALIZED if query is not inited
448 * STATUS.NULL_POINTER if search_messages failed
450 if self._query is None:
451 raise NotmuchError(STATUS.NOT_INITIALIZED)
453 msgs_p = Query._search_messages(self._query)
456 NotmuchError(STATUS.NULL_POINTER)
458 return Messages(msgs_p,self)
460 def count_messages(self):
461 """Estimate the number of messages matching the query
463 This function performs a search and returns Xapian's best
464 guess as to the number of matching messages. It is much faster
465 than performing :meth:`search_messages` and counting the
466 result with `len()` (although it always returned the same
467 result in my tests). Technically, it wraps the underlying
468 *notmuch_query_count_messages* function.
470 :returns: :class:`Messages`
471 :exception: :exc:`NotmuchError`
473 * STATUS.NOT_INITIALIZED if query is not inited
475 if self._query is None:
476 raise NotmuchError(STATUS.NOT_INITIALIZED)
478 return Query._count_messages(self._query)
481 """Close and free the Query"""
482 if self._query is not None:
483 logging.debug("Freeing the Query now")
484 nmlib.notmuch_query_destroy (self._query)
486 #------------------------------------------------------------------------------
488 """Represents a list of notmuch tags
490 This object provides an iterator over a list of notmuch tags. Do
491 note that the underlying library only provides a one-time iterator
492 (it cannot reset the iterator to the start). Thus iterating over
493 the function will "exhaust" the list of tags, and a subsequent
494 iteration attempt will raise a :exc:`NotmuchError`
495 STATUS.NOT_INITIALIZED. Also note, that any function that uses
496 iteration (nearly all) will also exhaust the tags. So both::
498 for tag in tags: print tag
502 number_of_tags = len(tags)
506 #str() iterates over all tags to construct a space separated list
509 will "exhaust" the Tags. If you need to re-iterate over a list of
510 tags you will need to retrieve a new :class:`Tags` object.
514 _get = nmlib.notmuch_tags_get
515 _get.restype = c_char_p
517 def __init__(self, tags_p, parent=None):
519 :param tags_p: A pointer to an underlying *notmuch_tags_t*
520 structure. These are not publically exposed, so a user
521 will almost never instantiate a :class:`Tags` object
522 herself. They are usually handed back as a result,
523 e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
524 valid, we will raise an :exc:`NotmuchError`
525 (STATUS.NULL_POINTER) if it is `None`.
526 :type tags_p: :class:`ctypes.c_void_p`
527 :param parent: The parent object (ie :class:`Database` or
528 :class:`Message` these tags are derived from, and saves a
529 reference to it, so we can automatically delete the db object
530 once all derived objects are dead.
531 :TODO: Make the iterator optionally work more than once by
532 cache the tags in the Python object(?)
535 NotmuchError(STATUS.NULL_POINTER)
538 #save reference to parent object so we keep it alive
539 self._parent = parent
540 logging.debug("Inited Tags derived from %s" %(repr(parent)))
543 """ Make Tags an iterator """
547 if self._tags is None:
548 raise NotmuchError(STATUS.NOT_INITIALIZED)
550 if not nmlib.notmuch_tags_valid(self._tags):
554 tag = Tags._get (self._tags)
555 nmlib.notmuch_tags_move_to_next(self._tags)
559 """len(:class:`Tags`) returns the number of contained tags
561 .. note:: As this iterates over the tags, we will not be able
562 to iterate over them again (as in retrieve them)! If
563 the tags have been exhausted already, this will raise a
564 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
567 if self._tags is None:
568 raise NotmuchError(STATUS.NOT_INITIALIZED)
571 while nmlib.notmuch_tags_valid(self._msgs):
572 nmlib.notmuch_tags_move_to_next(self._msgs)
578 """The str() representation of Tags() is a space separated list of tags
580 .. note:: As this iterates over the tags, we will not be able
581 to iterate over them again (as in retrieve them)! If
582 the tags have been exhausted already, this will raise a
583 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
586 return " ".join(self)
589 """Close and free the notmuch tags"""
590 if self._tags is not None:
591 logging.debug("Freeing the Tags now")
592 nmlib.notmuch_tags_destroy (self._tags)
595 #------------------------------------------------------------------------------
596 class Messages(object):
597 """Represents a list of notmuch messages
599 This object provides an iterator over a list of notmuch messages
600 (Technically, it provides a wrapper for the underlying
601 *notmuch_messages_t* structure). Do note that the underlying
602 library only provides a one-time iterator (it cannot reset the
603 iterator to the start). Thus iterating over the function will
604 "exhaust" the list of messages, and a subsequent iteration attempt
605 will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
606 note, that any function that uses iteration will also
607 exhaust the messages. So both::
609 for msg in msgs: print msg
613 number_of_msgs = len(msgs)
615 will "exhaust" the Messages. If you need to re-iterate over a list of
616 messages you will need to retrieve a new :class:`Messages` object.
618 Things are not as bad as it seems though, you can store and reuse
619 the single Message objects as often as you want as long as you
620 keep the parent Messages object around. (Recall that due to
621 hierarchical memory allocation, all derived Message objects will
622 be invalid when we delete the parent Messages() object, even if it
623 was already "exhausted".) So this works::
626 msgs = Query(db,'').search_messages() #get a Messages() object
631 # msgs is "exhausted" now and even len(msgs) will raise an exception.
632 # However it will be kept around until all retrieved Message() objects are
633 # also deleted. If you did e.g. an explicit del(msgs) here, the
634 # following lines would fail.
636 # You can reiterate over *msglist* however as often as you want.
637 # It is simply a list with Message objects.
639 print (msglist[0].get_filename())
640 print (msglist[1].get_filename())
641 print (msglist[0].get_message_id())
645 _get = nmlib.notmuch_messages_get
646 _get.restype = c_void_p
648 _collect_tags = nmlib.notmuch_messages_collect_tags
649 _collect_tags.restype = c_void_p
651 def __init__(self, msgs_p, parent=None):
653 :param msgs_p: A pointer to an underlying *notmuch_messages_t*
654 structure. These are not publically exposed, so a user
655 will almost never instantiate a :class:`Messages` object
656 herself. They are usually handed back as a result,
657 e.g. in :meth:`Query.search_messages`. *msgs_p* must be
658 valid, we will raise an :exc:`NotmuchError`
659 (STATUS.NULL_POINTER) if it is `None`.
660 :type msgs_p: :class:`ctypes.c_void_p`
661 :param parent: The parent object
662 (ie :class:`Query`) these tags are derived from. It saves
663 a reference to it, so we can automatically delete the db
664 object once all derived objects are dead.
665 :TODO: Make the iterator work more than once and cache the tags in
666 the Python object.(?)
669 NotmuchError(STATUS.NULL_POINTER)
672 #store parent, so we keep them alive as long as self is alive
673 self._parent = parent
674 logging.debug("Inited Messages derived from %s" %(str(parent)))
676 def collect_tags(self):
677 """Return the unique :class:`Tags` in the contained messages
679 :returns: :class:`Tags`
680 :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
682 .. note:: :meth:`collect_tags` will iterate over the messages and
683 therefore will not allow further iterations.
685 if self._msgs is None:
686 raise NotmuchError(STATUS.NOT_INITIALIZED)
688 # collect all tags (returns NULL on error)
689 tags_p = Messages._collect_tags (self._msgs)
690 #reset _msgs as we iterated over it and can do so only once
694 raise NotmuchError(STATUS.NULL_POINTER)
695 return Tags(tags_p, self)
698 """ Make Messages an iterator """
702 if self._msgs is None:
703 raise NotmuchError(STATUS.NOT_INITIALIZED)
705 if not nmlib.notmuch_messages_valid(self._msgs):
709 msg = Message(Messages._get (self._msgs), self)
710 nmlib.notmuch_messages_move_to_next(self._msgs)
714 """len(:class:`Messages`) returns the number of contained messages
716 .. note:: As this iterates over the messages, we will not be able to
717 iterate over them again! So this will fail::
720 msgs = Database().create_query('').search_message()
721 if len(msgs) > 0: #this 'exhausts' msgs
722 # next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!!
723 for msg in msgs: print msg
725 if self._msgs is None:
726 raise NotmuchError(STATUS.NOT_INITIALIZED)
729 while nmlib.notmuch_messages_valid(self._msgs):
730 nmlib.notmuch_messages_move_to_next(self._msgs)
738 """Close and free the notmuch Messages"""
739 if self._msgs is not None:
740 logging.debug("Freeing the Messages now")
741 nmlib.notmuch_messages_destroy (self._msgs)
744 #------------------------------------------------------------------------------
745 class Message(object):
746 """Represents a single Email message
748 Technically, this wraps the underlying *notmuch_message_t* structure.
751 """notmuch_message_get_filename (notmuch_message_t *message)"""
752 _get_filename = nmlib.notmuch_message_get_filename
753 _get_filename.restype = c_char_p
755 """notmuch_message_get_message_id (notmuch_message_t *message)"""
756 _get_message_id = nmlib.notmuch_message_get_message_id
757 _get_message_id.restype = c_char_p
759 """notmuch_message_get_thread_id"""
760 _get_thread_id = nmlib.notmuch_message_get_thread_id
761 _get_thread_id.restype = c_char_p
763 """notmuch_message_get_replies"""
764 _get_replies = nmlib.notmuch_message_get_replies
765 _get_replies.restype = c_void_p
767 """notmuch_message_get_tags (notmuch_message_t *message)"""
768 _get_tags = nmlib.notmuch_message_get_tags
769 _get_tags.restype = c_void_p
771 _get_date = nmlib.notmuch_message_get_date
772 _get_date.restype = c_uint64
774 _get_header = nmlib.notmuch_message_get_header
775 _get_header.restype = c_char_p
777 def __init__(self, msg_p, parent=None):
779 :param msg_p: A pointer to an internal notmuch_message_t
780 Structure. If it is `None`, we will raise an :exc:`NotmuchError`
782 :param parent: A 'parent' object is passed which this message is
783 derived from. We save a reference to it, so we can
784 automatically delete the parent object once all derived
788 NotmuchError(STATUS.NULL_POINTER)
790 #keep reference to parent, so we keep it alive
791 self._parent = parent
792 logging.debug("Inited Message derived from %s" %(str(parent)))
795 def get_message_id(self):
796 """Returns the message ID
798 :returns: String with a message ID
799 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
802 if self._msg is None:
803 raise NotmuchError(STATUS.NOT_INITIALIZED)
804 return Message._get_message_id(self._msg)
806 def get_thread_id(self):
807 """Returns the thread ID
809 The returned string belongs to 'message' will only be valid for as
810 long as the message is valid.
812 This function will not return None since Notmuch ensures that every
813 message belongs to a single thread.
815 :returns: String with a thread ID
816 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
819 if self._msg is None:
820 raise NotmuchError(STATUS.NOT_INITIALIZED)
822 return Message._get_thread_id (self._msg);
824 def get_replies(self):
825 """Gets all direct replies to this message as :class:`Messages` iterator
827 .. note:: This call only makes sense if 'message' was
828 ultimately obtained from a :class:`Thread` object, (such as
829 by coming directly from the result of calling
830 :meth:`Thread.get_toplevel_messages` or by any number of
831 subsequent calls to :meth:`get_replies`). If this message was
832 obtained through some non-thread means, (such as by a call
833 to :meth:`Query.search_messages`), then this function will
836 :returns: :class:`Messages` or `None` if there are no replies to
838 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
841 if self._msg is None:
842 raise NotmuchError(STATUS.NOT_INITIALIZED)
844 msgs_p = Message._get_replies(self._msg);
849 return Messages(msgs_p,self)
852 """Returns time_t of the message date
854 For the original textual representation of the Date header from the
855 message call notmuch_message_get_header() with a header value of
858 :returns: a time_t timestamp
860 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
863 if self._msg is None:
864 raise NotmuchError(STATUS.NOT_INITIALIZED)
865 return Message._get_date(self._msg)
867 def get_header(self, header):
868 """Returns a message header
870 This returns any message header that is stored in the notmuch database.
871 This is only a selected subset of headers, which is currently:
873 TODO: add stored headers
875 :param header: The name of the header to be retrieved.
876 It is not case-sensitive (TODO: confirm).
878 :returns: The header value as string
879 :exception: :exc:`NotmuchError`
881 * STATUS.NOT_INITIALIZED if the message
883 * STATUS.NULL_POINTER, if no header was found
885 if self._msg is None:
886 raise NotmuchError(STATUS.NOT_INITIALIZED)
888 #Returns NULL if any error occurs.
889 header = Message._get_header (self._msg, header)
891 raise NotmuchError(STATUS.NULL_POINTER)
894 def get_filename(self):
895 """Return the file path of the message file
897 :returns: Absolute file path & name of the message file
898 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
901 if self._msg is None:
902 raise NotmuchError(STATUS.NOT_INITIALIZED)
903 return Message._get_filename(self._msg)
906 """ Return the message tags
908 :returns: Message tags
909 :rtype: :class:`Tags`
910 :exception: :exc:`NotmuchError`
912 * STATUS.NOT_INITIALIZED if the message
914 * STATUS.NULL_POINTER, on error
916 if self._msg is None:
917 raise NotmuchError(STATUS.NOT_INITIALIZED)
919 tags_p = Message._get_tags(self._msg)
921 raise NotmuchError(STATUS.NULL_POINTER)
922 return Tags(tags_p, self)
924 def add_tag(self, tag):
925 """Add a tag to the given message
927 Adds a tag to the current message. The maximal tag length is defined in
928 the notmuch library and is currently 200 bytes.
930 :param tag: String with a 'tag' to be added.
931 :returns: STATUS.SUCCESS if the tag was successfully added.
932 Raises an exception otherwise.
933 :exception: :exc:`NotmuchError`. They have the following meaning:
936 The 'tag' argument is NULL
938 The length of 'tag' is too long
939 (exceeds Message.NOTMUCH_TAG_MAX)
940 STATUS.READ_ONLY_DATABASE
941 Database was opened in read-only mode so message cannot be
943 STATUS.NOT_INITIALIZED
944 The message has not been initialized.
946 if self._msg is None:
947 raise NotmuchError(STATUS.NOT_INITIALIZED)
949 status = nmlib.notmuch_message_add_tag (self._msg, tag)
951 if STATUS.SUCCESS == status:
955 raise NotmuchError(status)
957 def remove_tag(self, tag):
958 """Removes a tag from the given message
960 If the message has no such tag, this is a non-operation and
961 will report success anyway.
963 :param tag: String with a 'tag' to be removed.
964 :returns: STATUS.SUCCESS if the tag was successfully removed or if
965 the message had no such tag.
966 Raises an exception otherwise.
967 :exception: :exc:`NotmuchError`. They have the following meaning:
970 The 'tag' argument is NULL
972 The length of 'tag' is too long
973 (exceeds NOTMUCH_TAG_MAX)
974 STATUS.READ_ONLY_DATABASE
975 Database was opened in read-only mode so message cannot
977 STATUS.NOT_INITIALIZED
978 The message has not been initialized.
980 if self._msg is None:
981 raise NotmuchError(STATUS.NOT_INITIALIZED)
983 status = nmlib.notmuch_message_remove_tag(self._msg, tag)
985 if STATUS.SUCCESS == status:
989 raise NotmuchError(status)
991 def remove_all_tags(self):
992 """Removes all tags from the given message.
994 See :meth:`freeze` for an example showing how to safely
997 :returns: STATUS.SUCCESS if the tags were successfully removed.
998 Raises an exception otherwise.
999 :exception: :exc:`NotmuchError`. They have the following meaning:
1001 STATUS.READ_ONLY_DATABASE
1002 Database was opened in read-only mode so message cannot
1004 STATUS.NOT_INITIALIZED
1005 The message has not been initialized.
1007 if self._msg is None:
1008 raise NotmuchError(STATUS.NOT_INITIALIZED)
1010 status = nmlib.notmuch_message_remove_all_tags(self._msg)
1012 if STATUS.SUCCESS == status:
1016 raise NotmuchError(status)
1019 """Freezes the current state of 'message' within the database
1021 This means that changes to the message state, (via :meth:`add_tag`,
1022 :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
1023 committed to the database until the message is :meth:`thaw`ed.
1025 Multiple calls to freeze/thaw are valid and these calls will
1026 "stack". That is there must be as many calls to thaw as to freeze
1027 before a message is actually thawed.
1029 The ability to do freeze/thaw allows for safe transactions to
1030 change tag values. For example, explicitly setting a message to
1031 have a given set of tags might look like this::
1034 msg.remove_all_tags()
1035 for tag in new_tags:
1039 With freeze/thaw used like this, the message in the database is
1040 guaranteed to have either the full set of original tag values, or
1041 the full set of new tag values, but nothing in between.
1043 Imagine the example above without freeze/thaw and the operation
1044 somehow getting interrupted. This could result in the message being
1045 left with no tags if the interruption happened after
1046 :meth:`remove_all_tags` but before :meth:`add_tag`.
1048 :returns: STATUS.SUCCESS if the message was successfully frozen.
1049 Raises an exception otherwise.
1050 :exception: :exc:`NotmuchError`. They have the following meaning:
1052 STATUS.READ_ONLY_DATABASE
1053 Database was opened in read-only mode so message cannot
1055 STATUS.NOT_INITIALIZED
1056 The message has not been initialized.
1058 if self._msg is None:
1059 raise NotmuchError(STATUS.NOT_INITIALIZED)
1061 status = nmlib.notmuch_message_freeze(self._msg)
1063 if STATUS.SUCCESS == status:
1067 raise NotmuchError(status)
1070 """Thaws the current 'message'
1072 Thaw the current 'message', synchronizing any changes that may have
1073 occurred while 'message' was frozen into the notmuch database.
1075 See :meth:`freeze` for an example of how to use this
1076 function to safely provide tag changes.
1078 Multiple calls to freeze/thaw are valid and these calls with
1079 "stack". That is there must be as many calls to thaw as to freeze
1080 before a message is actually thawed.
1082 :returns: STATUS.SUCCESS if the message was successfully frozen.
1083 Raises an exception otherwise.
1084 :exception: :exc:`NotmuchError`. They have the following meaning:
1086 STATUS.UNBALANCED_FREEZE_THAW
1087 An attempt was made to thaw an unfrozen message.
1088 That is, there have been an unbalanced number of calls
1089 to :meth:`freeze` and :meth:`thaw`.
1090 STATUS.NOT_INITIALIZED
1091 The message has not been initialized.
1093 if self._msg is None:
1094 raise NotmuchError(STATUS.NOT_INITIALIZED)
1096 status = nmlib.notmuch_message_thaw(self._msg)
1098 if STATUS.SUCCESS == status:
1102 raise NotmuchError(status)
1106 """A message() is represented by a 1-line summary"""
1108 msg['from'] = self.get_header('from')
1109 msg['tags'] = str(self.get_tags())
1110 msg['date'] = date.fromtimestamp(self.get_date())
1111 replies = self.get_replies()
1112 msg['replies'] = len(replies) if replies is not None else -1
1113 return "%(from)s (%(date)s) (%(tags)s) (%(replies)d) replies" % (msg)
1115 def format_as_text(self):
1116 """Output like notmuch show (Not implemented)"""
1120 """Close and free the notmuch Message"""
1121 if self._msg is not None:
1122 logging.debug("Freeing the Message now")
1123 nmlib.notmuch_message_destroy (self._msg)