2 from ctypes import c_int, c_char_p, c_void_p, c_uint, c_uint64, c_bool
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 create(self, path):
81 """Creates a new notmuch database
83 This function is used by __init__() and usually does not need
84 to be called directly. It wraps the underlying
85 *notmuch_database_create* function and creates a new notmuch
86 database at *path*. It will always return a database in
87 :attr:`MODE`.READ_WRITE mode as creating an empty database for
88 reading only does not make a great deal of sense.
90 :param path: A directory in which we should create the database.
93 :exception: :exc:`NotmuchError` in case of any failure
94 (after printing an error message on stderr).
96 if self._db is not None:
98 message="Cannot create db, this Database() already has an open one.")
100 res = Database._create(path, Database.MODE.READ_WRITE)
104 message="Could not create the specified database")
107 def open(self, path, mode= 0):
108 """Opens an existing database
110 This function is used by __init__() and usually does not need
111 to be called directly. It wraps the underlying
112 *notmuch_database_open* function.
114 :param status: Open the database in read-only or read-write mode
115 :type status: :attr:`MODE`
117 :exception: Raises :exc:`NotmuchError` in case
118 of any failure (after printing an error message on stderr).
121 res = Database._open(path, mode)
125 message="Could not open the specified database")
129 """Returns the file path of an open database
131 Wraps notmuch_database_get_path"""
132 return Database._get_path(self._db)
134 def get_version(self):
135 """Returns the database format version
137 :returns: The database version as positive integer
138 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
139 the database was not intitialized.
142 raise NotmuchError(STATUS.NOT_INITIALIZED)
144 return Database._get_version (self._db)
146 def needs_upgrade(self):
147 """Does this database need to be upgraded before writing to it?
149 If this function returns True then no functions that modify the
150 database (:meth:`add_message`, :meth:`add_tag`,
151 :meth:`Directory.set_mtime`, etc.) will work unless :meth:`upgrade`
152 is called successfully first.
154 :returns: `True` or `False`
155 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
156 the database was not intitialized.
159 raise NotmuchError(STATUS.NOT_INITIALIZED)
161 return notmuch_database_needs_upgrade(self.db)
163 def find_message(self, msgid):
164 """Returns a :class:`Message` as identified by its message ID
166 Wraps the underlying *notmuch_database_find_message* function.
168 :param msgid: The message ID
170 :returns: :class:`Message` or `None` if no message is found or if an
171 out-of-memory situation occurs.
172 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
173 the database was not intitialized.
176 raise NotmuchError(STATUS.NOT_INITIALIZED)
177 msg_p = Database._find_message(self._db, msgid)
180 return Message(msg_p, self)
182 def get_all_tags(self):
183 """Returns :class:`Tags` with a list of all tags found in the database
185 :returns: :class:`Tags`
186 :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
189 raise NotmuchError(STATUS.NOT_INITIALIZED)
191 tags_p = Database._get_all_tags (self._db)
193 raise NotmuchError(STATUS.NULL_POINTER)
194 return Tags(tags_p, self)
197 return "'Notmuch DB " + self.get_path() + "'"
200 """Close and free the notmuch database if needed"""
201 if self._db is not None:
202 logging.debug("Freeing the database now")
203 nmlib.notmuch_database_close(self._db)
205 def _get_user_default_db(self):
206 """ Reads a user's notmuch config and returns his db location
208 Throws a NotmuchError if it cannot find it"""
209 from ConfigParser import SafeConfigParser
210 config = SafeConfigParser()
211 conf_f = os.getenv('NOTMUCH_CONFIG',
212 os.path.expanduser('~/.notmuch-config'))
214 if not config.has_option('database','path'):
215 raise NotmuchError(message=
216 "No DB path specified and no user default found")
217 return config.get('database','path')
221 """Property returning a pointer to the notmuch_database_t or `None`
223 This should normally not be needed by a user."""
226 #------------------------------------------------------------------------------
228 """ Represents a search query on an opened :class:`Database`.
230 A query selects and filters a subset of messages from the notmuch
231 database we derive from.
233 Technically, it wraps the underlying *notmuch_query_t* struct.
235 .. note:: Do remember that as soon as we tear down this object,
236 all underlying derived objects such as threads,
237 messages, tags etc will be freed by the underlying library
238 as well. Accessing these objects will lead to segfaults and
239 other unexpected behavior. See above for more details.
242 SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
243 """Constants: Sort order in which to return results"""
245 """notmuch_query_create"""
246 _create = nmlib.notmuch_query_create
247 _create.restype = c_void_p
249 """notmuch_query_search_messages"""
250 _search_messages = nmlib.notmuch_query_search_messages
251 _search_messages.restype = c_void_p
254 """notmuch_query_count_messages"""
255 _count_messages = nmlib.notmuch_query_count_messages
256 _count_messages.restype = c_uint
258 def __init__(self, db, querystr):
260 :param db: An open database which we derive the Query from.
261 :type db: :class:`Database`
262 :param querystr: The query string for the message.
267 self.create(db, querystr)
269 def create(self, db, querystr):
270 """Creates a new query derived from a Database.
272 This function is utilized by __init__() and usually does not need to
275 :param db: Database to create the query from.
276 :type db: :class:`Database`
277 :param querystr: The query string
280 :exception: :exc:`NotmuchError`
282 * STATUS.NOT_INITIALIZED if db is not inited
283 * STATUS.NULL_POINTER if the query creation failed
287 raise NotmuchError(STATUS.NOT_INITIALIZED)
288 # create reference to parent db to keep it alive
291 # create query, return None if too little mem available
292 query_p = Query._create(db.db_p, querystr)
294 NotmuchError(STATUS.NULL_POINTER)
295 self._query = query_p
297 def set_sort(self, sort):
298 """Set the sort order future results will be delivered in
300 Wraps the underlying *notmuch_query_set_sort* function.
302 :param sort: Sort order (see :attr:`Query.SORT`)
304 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not
307 if self._query is None:
308 raise NotmuchError(STATUS.NOT_INITIALIZED)
310 nmlib.notmuch_query_set_sort(self._query, sort)
312 def search_messages(self):
313 """Filter messages according to the query and return
314 :class:`Messages` in the defined sort order
316 Technically, it wraps the underlying
317 *notmuch_query_search_messages* function.
319 :returns: :class:`Messages`
320 :exception: :exc:`NotmuchError`
322 * STATUS.NOT_INITIALIZED if query is not inited
323 * STATUS.NULL_POINTER if search_messages failed
325 if self._query is None:
326 raise NotmuchError(STATUS.NOT_INITIALIZED)
328 msgs_p = Query._search_messages(self._query)
331 NotmuchError(STATUS.NULL_POINTER)
333 return Messages(msgs_p,self)
335 def count_messages(self):
336 """Estimate the number of messages matching the query
338 This function performs a search and returns Xapian's best
339 guess as to the number of matching messages. It is much faster
340 than performing :meth:`search_messages` and counting the
341 result with `len()` (although it always returned the same
342 result in my tests). Technically, it wraps the underlying
343 *notmuch_query_count_messages* function.
345 :returns: :class:`Messages`
346 :exception: :exc:`NotmuchError`
348 * STATUS.NOT_INITIALIZED if query is not inited
350 if self._query is None:
351 raise NotmuchError(STATUS.NOT_INITIALIZED)
353 return Query._count_messages(self._query)
356 """Close and free the Query"""
357 if self._query is not None:
358 logging.debug("Freeing the Query now")
359 nmlib.notmuch_query_destroy (self._query)
361 #------------------------------------------------------------------------------
363 """Represents a list of notmuch tags
365 This object provides an iterator over a list of notmuch tags. Do
366 note that the underlying library only provides a one-time iterator
367 (it cannot reset the iterator to the start). Thus iterating over
368 the function will "exhaust" the list of tags, and a subsequent
369 iteration attempt will raise a :exc:`NotmuchError`
370 STATUS.NOT_INITIALIZED. Also note, that any function that uses
371 iteration (nearly all) will also exhaust the tags. So both::
373 for tag in tags: print tag
377 number_of_tags = len(tags)
381 #str() iterates over all tags to construct a space separated list
384 will "exhaust" the Tags. If you need to re-iterate over a list of
385 tags you will need to retrieve a new :class:`Tags` object.
389 _get = nmlib.notmuch_tags_get
390 _get.restype = c_char_p
392 def __init__(self, tags_p, parent=None):
394 :param tags_p: A pointer to an underlying *notmuch_tags_t*
395 structure. These are not publically exposed, so a user
396 will almost never instantiate a :class:`Tags` object
397 herself. They are usually handed back as a result,
398 e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
399 valid, we will raise an :exc:`NotmuchError`
400 (STATUS.NULL_POINTER) if it is `None`.
401 :type tags_p: :class:`ctypes.c_void_p`
402 :param parent: The parent object (ie :class:`Database` or
403 :class:`Message` these tags are derived from, and saves a
404 reference to it, so we can automatically delete the db object
405 once all derived objects are dead.
406 :TODO: Make the iterator optionally work more than once by
407 cache the tags in the Python object(?)
410 NotmuchError(STATUS.NULL_POINTER)
413 #save reference to parent object so we keep it alive
414 self._parent = parent
415 logging.debug("Inited Tags derived from %s" %(repr(parent)))
418 """ Make Tags an iterator """
422 if self._tags is None:
423 raise NotmuchError(STATUS.NOT_INITIALIZED)
425 if not nmlib.notmuch_tags_valid(self._tags):
429 tag = Tags._get (self._tags)
430 nmlib.notmuch_tags_move_to_next(self._tags)
434 """len(:class:`Tags`) returns the number of contained tags
436 .. note:: As this iterates over the tags, we will not be able
437 to iterate over them again (as in retrieve them)! If
438 the tags have been exhausted already, this will raise a
439 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
442 if self._tags is None:
443 raise NotmuchError(STATUS.NOT_INITIALIZED)
446 while nmlib.notmuch_tags_valid(self._msgs):
447 nmlib.notmuch_tags_move_to_next(self._msgs)
453 """The str() representation of Tags() is a space separated list of tags
455 .. note:: As this iterates over the tags, we will not be able
456 to iterate over them again (as in retrieve them)! If
457 the tags have been exhausted already, this will raise a
458 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
461 return " ".join(self)
464 """Close and free the notmuch tags"""
465 if self._tags is not None:
466 logging.debug("Freeing the Tags now")
467 nmlib.notmuch_tags_destroy (self._tags)
470 #------------------------------------------------------------------------------
471 class Messages(object):
472 """Represents a list of notmuch messages
474 This object provides an iterator over a list of notmuch messages
475 (Technically, it provides a wrapper for the underlying
476 *notmuch_messages_t* structure). Do note that the underlying
477 library only provides a one-time iterator (it cannot reset the
478 iterator to the start). Thus iterating over the function will
479 "exhaust" the list of messages, and a subsequent iteration attempt
480 will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
481 note, that any function that uses iteration will also
482 exhaust the messages. So both::
484 for msg in msgs: print msg
488 number_of_msgs = len(msgs)
490 will "exhaust" the Messages. If you need to re-iterate over a list of
491 messages you will need to retrieve a new :class:`Messages` object.
493 Things are not as bad as it seems though, you can store and reuse
494 the single Message objects as often as you want as long as you
495 keep the parent Messages object around. (Recall that due to
496 hierarchical memory allocation, all derived Message objects will
497 be invalid when we delete the parent Messages() object, even if it
498 was already "exhausted".) So this works::
501 msgs = Query(db,'').search_messages() #get a Messages() object
506 # msgs is "exhausted" now and even len(msgs) will raise an exception.
507 # However it will be kept around until all retrieved Message() objects are
508 # also deleted. If you did e.g. an explicit del(msgs) here, the
509 # following lines would fail.
511 # You can reiterate over *msglist* however as often as you want.
512 # It is simply a list with Message objects.
514 print (msglist[0].get_filename())
515 print (msglist[1].get_filename())
516 print (msglist[0].get_message_id())
520 _get = nmlib.notmuch_messages_get
521 _get.restype = c_void_p
523 _collect_tags = nmlib.notmuch_messages_collect_tags
524 _collect_tags.restype = c_void_p
526 def __init__(self, msgs_p, parent=None):
528 :param msgs_p: A pointer to an underlying *notmuch_messages_t*
529 structure. These are not publically exposed, so a user
530 will almost never instantiate a :class:`Messages` object
531 herself. They are usually handed back as a result,
532 e.g. in :meth:`Query.search_messages`. *msgs_p* must be
533 valid, we will raise an :exc:`NotmuchError`
534 (STATUS.NULL_POINTER) if it is `None`.
535 :type msgs_p: :class:`ctypes.c_void_p`
536 :param parent: The parent object
537 (ie :class:`Query`) these tags are derived from. It saves
538 a reference to it, so we can automatically delete the db
539 object once all derived objects are dead.
540 :TODO: Make the iterator work more than once and cache the tags in
541 the Python object.(?)
544 NotmuchError(STATUS.NULL_POINTER)
547 #store parent, so we keep them alive as long as self is alive
548 self._parent = parent
549 logging.debug("Inited Messages derived from %s" %(str(parent)))
551 def collect_tags(self):
552 """Return the unique :class:`Tags` in the contained messages
554 :returns: :class:`Tags`
555 :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
557 .. note:: :meth:`collect_tags` will iterate over the messages and
558 therefore will not allow further iterations.
560 if self._msgs is None:
561 raise NotmuchError(STATUS.NOT_INITIALIZED)
563 # collect all tags (returns NULL on error)
564 tags_p = Messages._collect_tags (self._msgs)
565 #reset _msgs as we iterated over it and can do so only once
569 raise NotmuchError(STATUS.NULL_POINTER)
570 return Tags(tags_p, self)
573 """ Make Messages an iterator """
577 if self._msgs is None:
578 raise NotmuchError(STATUS.NOT_INITIALIZED)
580 if not nmlib.notmuch_messages_valid(self._msgs):
584 msg = Message(Messages._get (self._msgs), self)
585 nmlib.notmuch_messages_move_to_next(self._msgs)
589 """len(:class:`Messages`) returns the number of contained messages
591 .. note:: As this iterates over the messages, we will not be able to
592 iterate over them again (as in retrieve them)!
594 if self._msgs is None:
595 raise NotmuchError(STATUS.NOT_INITIALIZED)
598 while nmlib.notmuch_messages_valid(self._msgs):
599 nmlib.notmuch_messages_move_to_next(self._msgs)
607 """Close and free the notmuch Messages"""
608 if self._msgs is not None:
609 logging.debug("Freeing the Messages now")
610 nmlib.notmuch_messages_destroy (self._msgs)
613 #------------------------------------------------------------------------------
614 class Message(object):
615 """Represents a single Email message
617 Technically, this wraps the underlying *notmuch_message_t* structure.
620 """notmuch_message_get_filename (notmuch_message_t *message)"""
621 _get_filename = nmlib.notmuch_message_get_filename
622 _get_filename.restype = c_char_p
623 """notmuch_message_get_message_id (notmuch_message_t *message)"""
624 _get_message_id = nmlib.notmuch_message_get_message_id
625 _get_message_id.restype = c_char_p
627 """notmuch_message_get_tags (notmuch_message_t *message)"""
628 _get_tags = nmlib.notmuch_message_get_tags
629 _get_tags.restype = c_void_p
631 _get_date = nmlib.notmuch_message_get_date
632 _get_date.restype = c_uint64
634 _get_header = nmlib.notmuch_message_get_header
635 _get_header.restype = c_char_p
637 def __init__(self, msg_p, parent=None):
639 :param msg_p: A pointer to an internal notmuch_message_t
640 Structure. If it is `None`, we will raise an :exc:`NotmuchError`
642 :param parent: A 'parent' object is passed which this message is
643 derived from. We save a reference to it, so we can
644 automatically delete the parent object once all derived
648 NotmuchError(STATUS.NULL_POINTER)
650 #keep reference to parent, so we keep it alive
651 self._parent = parent
652 logging.debug("Inited Message derived from %s" %(str(parent)))
655 def get_message_id(self):
656 """Return the message ID
658 :returns: String with a message ID
659 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
662 if self._msg is None:
663 raise NotmuchError(STATUS.NOT_INITIALIZED)
664 return Message._get_message_id(self._msg)
667 """Returns time_t of the message date
669 For the original textual representation of the Date header from the
670 message call notmuch_message_get_header() with a header value of
673 :returns: a time_t timestamp
675 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
678 if self._msg is None:
679 raise NotmuchError(STATUS.NOT_INITIALIZED)
680 return Message._get_date(self._msg)
682 def get_header(self, header):
683 """Returns a message header
685 This returns any message header that is stored in the notmuch database.
686 This is only a selected subset of headers, which is currently:
688 TODO: add stored headers
690 :param header: The name of the header to be retrieved.
691 It is not case-sensitive (TODO: confirm).
693 :returns: The header value as string
694 :exception: :exc:`NotmuchError`
696 * STATUS.NOT_INITIALIZED if the message
698 * STATUS.NULL_POINTER, if no header was found
700 if self._msg is None:
701 raise NotmuchError(STATUS.NOT_INITIALIZED)
703 #Returns NULL if any error occurs.
704 header = Message._get_header (self._msg, header)
706 raise NotmuchError(STATUS.NULL_POINTER)
709 def get_filename(self):
710 """Return the file path of the message file
712 :returns: Absolute file path & name of the message file
713 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
716 if self._msg is None:
717 raise NotmuchError(STATUS.NOT_INITIALIZED)
718 return Message._get_filename(self._msg)
721 """ Return the message tags
723 :returns: Message tags
724 :rtype: :class:`Tags`
725 :exception: :exc:`NotmuchError`
727 * STATUS.NOT_INITIALIZED if the message
729 * STATUS.NULL_POINTER, on error
731 if self._msg is None:
732 raise NotmuchError(STATUS.NOT_INITIALIZED)
734 tags_p = Message._get_tags(self._msg)
736 raise NotmuchError(STATUS.NULL_POINTER)
737 return Tags(tags_p, self)
739 def add_tag(self, tag):
740 """Add a tag to the given message
742 Adds a tag to the current message. The maximal tag length is defined in
743 the notmuch library and is currently 200 bytes.
745 :param tag: String with a 'tag' to be added.
746 :returns: STATUS.SUCCESS if the tag was successfully added.
747 Raises an exception otherwise.
748 :exception: :exc:`NotmuchError`. They have the following meaning:
751 The 'tag' argument is NULL
753 The length of 'tag' is too long
754 (exceeds Message.NOTMUCH_TAG_MAX)
755 STATUS.READ_ONLY_DATABASE
756 Database was opened in read-only mode so message cannot be
758 STATUS.NOT_INITIALIZED
759 The message has not been initialized.
761 if self._msg is None:
762 raise NotmuchError(STATUS.NOT_INITIALIZED)
764 status = nmlib.notmuch_message_add_tag (self._msg, tag)
766 if STATUS.SUCCESS == status:
770 raise NotmuchError(status)
772 def remove_tag(self, tag):
773 """Removes a tag from the given message
775 If the message has no such tag, this is a non-operation and
776 will report success anyway.
778 :param tag: String with a 'tag' to be removed.
779 :returns: STATUS.SUCCESS if the tag was successfully removed or if
780 the message had no such tag.
781 Raises an exception otherwise.
782 :exception: :exc:`NotmuchError`. They have the following meaning:
785 The 'tag' argument is NULL
787 The length of 'tag' is too long
788 (exceeds NOTMUCH_TAG_MAX)
789 STATUS.READ_ONLY_DATABASE
790 Database was opened in read-only mode so message cannot
792 STATUS.NOT_INITIALIZED
793 The message has not been initialized.
795 if self._msg is None:
796 raise NotmuchError(STATUS.NOT_INITIALIZED)
798 status = nmlib.notmuch_message_remove_tag(self._msg, tag)
800 if STATUS.SUCCESS == status:
804 raise NotmuchError(status)
806 def remove_all_tags(self):
807 """Removes all tags from the given message.
809 See :meth:`freeze` for an example showing how to safely
812 :returns: STATUS.SUCCESS if the tags were successfully removed.
813 Raises an exception otherwise.
814 :exception: :exc:`NotmuchError`. They have the following meaning:
816 STATUS.READ_ONLY_DATABASE
817 Database was opened in read-only mode so message cannot
819 STATUS.NOT_INITIALIZED
820 The message has not been initialized.
822 if self._msg is None:
823 raise NotmuchError(STATUS.NOT_INITIALIZED)
825 status = nmlib.notmuch_message_remove_all_tags(self._msg)
827 if STATUS.SUCCESS == status:
831 raise NotmuchError(status)
834 """Freezes the current state of 'message' within the database
836 This means that changes to the message state, (via :meth:`add_tag`,
837 :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
838 committed to the database until the message is :meth:`thaw`ed.
840 Multiple calls to freeze/thaw are valid and these calls will
841 "stack". That is there must be as many calls to thaw as to freeze
842 before a message is actually thawed.
844 The ability to do freeze/thaw allows for safe transactions to
845 change tag values. For example, explicitly setting a message to
846 have a given set of tags might look like this::
849 msg.remove_all_tags()
854 With freeze/thaw used like this, the message in the database is
855 guaranteed to have either the full set of original tag values, or
856 the full set of new tag values, but nothing in between.
858 Imagine the example above without freeze/thaw and the operation
859 somehow getting interrupted. This could result in the message being
860 left with no tags if the interruption happened after
861 :meth:`remove_all_tags` but before :meth:`add_tag`.
863 :returns: STATUS.SUCCESS if the message was successfully frozen.
864 Raises an exception otherwise.
865 :exception: :exc:`NotmuchError`. They have the following meaning:
867 STATUS.READ_ONLY_DATABASE
868 Database was opened in read-only mode so message cannot
870 STATUS.NOT_INITIALIZED
871 The message has not been initialized.
873 if self._msg is None:
874 raise NotmuchError(STATUS.NOT_INITIALIZED)
876 status = nmlib.notmuch_message_freeze(self._msg)
878 if STATUS.SUCCESS == status:
882 raise NotmuchError(status)
885 """Thaws the current 'message'
887 Thaw the current 'message', synchronizing any changes that may have
888 occurred while 'message' was frozen into the notmuch database.
890 See :meth:`freeze` for an example of how to use this
891 function to safely provide tag changes.
893 Multiple calls to freeze/thaw are valid and these calls with
894 "stack". That is there must be as many calls to thaw as to freeze
895 before a message is actually thawed.
897 :returns: STATUS.SUCCESS if the message was successfully frozen.
898 Raises an exception otherwise.
899 :exception: :exc:`NotmuchError`. They have the following meaning:
901 STATUS.UNBALANCED_FREEZE_THAW
902 An attempt was made to thaw an unfrozen message.
903 That is, there have been an unbalanced number of calls
904 to :meth:`freeze` and :meth:`thaw`.
905 STATUS.NOT_INITIALIZED
906 The message has not been initialized.
908 if self._msg is None:
909 raise NotmuchError(STATUS.NOT_INITIALIZED)
911 status = nmlib.notmuch_message_thaw(self._msg)
913 if STATUS.SUCCESS == status:
917 raise NotmuchError(status)
921 """A message() is represented by a 1-line summary"""
923 msg['from'] = self.get_header('from')
924 msg['tags'] = str(self.get_tags())
925 msg['date'] = date.fromtimestamp(self.get_date())
926 return "%(from)s (%(date)s) (%(tags)s)" % (msg)
928 def format_as_text(self):
929 """Output like notmuch show (Not implemented)"""
933 """Close and free the notmuch Message"""
934 if self._msg is not None:
935 logging.debug("Freeing the Message now")
936 nmlib.notmuch_message_destroy (self._msg)