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 somewhat
340 faster than performing :meth:`search_messages` and counting
341 the result with `len()`. Technically, it wraps the underlying
342 *notmuch_query_count_messages* function.
344 :returns: :class:`Messages`
345 :exception: :exc:`NotmuchError`
347 * STATUS.NOT_INITIALIZED if query is not inited
349 if self._query is None:
350 raise NotmuchError(STATUS.NOT_INITIALIZED)
352 return Query._count_messages(self._query)
355 """Close and free the Query"""
356 if self._query is not None:
357 logging.debug("Freeing the Query now")
358 nmlib.notmuch_query_destroy (self._query)
360 #------------------------------------------------------------------------------
362 """Represents a list of notmuch tags
364 This object provides an iterator over a list of notmuch tags. Do
365 note that the underlying library only provides a one-time iterator
366 (it cannot reset the iterator to the start). Thus iterating over
367 the function will "exhaust" the list of tags, and a subsequent
368 iteration attempt will raise a :exc:`NotmuchError`
369 STATUS.NOT_INITIALIZED. Also note, that any function that uses
370 iteration (nearly all) will also exhaust the tags. So both::
372 for tag in tags: print tag
376 number_of_tags = len(tags)
380 #str() iterates over all tags to construct a space separated list
383 will "exhaust" the Tags. If you need to re-iterate over a list of
384 tags you will need to retrieve a new :class:`Tags` object.
388 _get = nmlib.notmuch_tags_get
389 _get.restype = c_char_p
391 def __init__(self, tags_p, parent=None):
393 :param tags_p: A pointer to an underlying *notmuch_tags_t*
394 structure. These are not publically exposed, so a user
395 will almost never instantiate a :class:`Tags` object
396 herself. They are usually handed back as a result,
397 e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
398 valid, we will raise an :exc:`NotmuchError`
399 (STATUS.NULL_POINTER) if it is `None`.
400 :type tags_p: :class:`ctypes.c_void_p`
401 :param parent: The parent object (ie :class:`Database` or
402 :class:`Message` these tags are derived from, and saves a
403 reference to it, so we can automatically delete the db object
404 once all derived objects are dead.
405 :TODO: Make the iterator optionally work more than once by
406 cache the tags in the Python object(?)
409 NotmuchError(STATUS.NULL_POINTER)
412 #save reference to parent object so we keep it alive
413 self._parent = parent
414 logging.debug("Inited Tags derived from %s" %(repr(parent)))
417 """ Make Tags an iterator """
421 if self._tags is None:
422 raise NotmuchError(STATUS.NOT_INITIALIZED)
424 if not nmlib.notmuch_tags_valid(self._tags):
428 tag = Tags._get (self._tags)
429 nmlib.notmuch_tags_move_to_next(self._tags)
433 """len(:class:`Tags`) returns the number of contained tags
435 .. note:: As this iterates over the tags, we will not be able
436 to iterate over them again (as in retrieve them)! If
437 the tags have been exhausted already, this will raise a
438 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
441 if self._tags is None:
442 raise NotmuchError(STATUS.NOT_INITIALIZED)
445 while nmlib.notmuch_tags_valid(self._msgs):
446 nmlib.notmuch_tags_move_to_next(self._msgs)
452 """The str() representation of Tags() is a space separated list of tags
454 .. note:: As this iterates over the tags, we will not be able
455 to iterate over them again (as in retrieve them)! If
456 the tags have been exhausted already, this will raise a
457 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
460 return " ".join(self)
463 """Close and free the notmuch tags"""
464 if self._tags is not None:
465 logging.debug("Freeing the Tags now")
466 nmlib.notmuch_tags_destroy (self._tags)
469 #------------------------------------------------------------------------------
470 class Messages(object):
471 """Represents a list of notmuch messages
473 This object provides an iterator over a list of notmuch messages
474 (Technically, it provides a wrapper for the underlying
475 *notmuch_messages_t* structure). Do note that the underlying
476 library only provides a one-time iterator (it cannot reset the
477 iterator to the start). Thus iterating over the function will
478 "exhaust" the list of messages, and a subsequent iteration attempt
479 will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
480 note, that any function that uses iteration will also
481 exhaust the messages. So both::
483 for msg in msgs: print msg
487 number_of_msgs = len(msgs)
489 will "exhaust" the Messages. If you need to re-iterate over a list of
490 messages you will need to retrieve a new :class:`Messages` object.
492 Things are not as bad as it seems though, you can store and reuse
493 the single Message objects as often as you want as long as you
494 keep the parent Messages object around. (Recall that due to
495 hierarchical memory allocation, all derived Message objects will
496 be invalid when we delete the parent Messages() object, even if it
497 was already "exhausted".) So this works::
500 msgs = Query(db,'').search_messages() #get a Messages() object
505 # msgs is "exhausted" now and even len(msgs) will raise an exception.
506 # However it will be kept around until all retrieved Message() objects are
507 # also deleted. If you did e.g. an explicit del(msgs) here, the
508 # following lines would fail.
510 # You can reiterate over *msglist* however as often as you want.
511 # It is simply a list with Message objects.
513 print (msglist[0].get_filename())
514 print (msglist[1].get_filename())
515 print (msglist[0].get_message_id())
519 _get = nmlib.notmuch_messages_get
520 _get.restype = c_void_p
522 _collect_tags = nmlib.notmuch_messages_collect_tags
523 _collect_tags.restype = c_void_p
525 def __init__(self, msgs_p, parent=None):
527 :param msgs_p: A pointer to an underlying *notmuch_messages_t*
528 structure. These are not publically exposed, so a user
529 will almost never instantiate a :class:`Messages` object
530 herself. They are usually handed back as a result,
531 e.g. in :meth:`Query.search_messages`. *msgs_p* must be
532 valid, we will raise an :exc:`NotmuchError`
533 (STATUS.NULL_POINTER) if it is `None`.
534 :type msgs_p: :class:`ctypes.c_void_p`
535 :param parent: The parent object
536 (ie :class:`Query`) these tags are derived from. It saves
537 a reference to it, so we can automatically delete the db
538 object once all derived objects are dead.
539 :TODO: Make the iterator work more than once and cache the tags in
540 the Python object.(?)
543 NotmuchError(STATUS.NULL_POINTER)
546 #store parent, so we keep them alive as long as self is alive
547 self._parent = parent
548 logging.debug("Inited Messages derived from %s" %(str(parent)))
550 def collect_tags(self):
551 """Return the unique :class:`Tags` in the contained messages
553 :returns: :class:`Tags`
554 :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
556 .. note:: :meth:`collect_tags` will iterate over the messages and
557 therefore will not allow further iterations.
559 if self._msgs is None:
560 raise NotmuchError(STATUS.NOT_INITIALIZED)
562 # collect all tags (returns NULL on error)
563 tags_p = Messages._collect_tags (self._msgs)
564 #reset _msgs as we iterated over it and can do so only once
568 raise NotmuchError(STATUS.NULL_POINTER)
569 return Tags(tags_p, self)
572 """ Make Messages an iterator """
576 if self._msgs is None:
577 raise NotmuchError(STATUS.NOT_INITIALIZED)
579 if not nmlib.notmuch_messages_valid(self._msgs):
583 msg = Message(Messages._get (self._msgs), self)
584 nmlib.notmuch_messages_move_to_next(self._msgs)
588 """len(:class:`Messages`) returns the number of contained messages
590 .. note:: As this iterates over the messages, we will not be able to
591 iterate over them again (as in retrieve them)!
593 if self._msgs is None:
594 raise NotmuchError(STATUS.NOT_INITIALIZED)
597 while nmlib.notmuch_messages_valid(self._msgs):
598 nmlib.notmuch_messages_move_to_next(self._msgs)
606 """Close and free the notmuch Messages"""
607 if self._msgs is not None:
608 logging.debug("Freeing the Messages now")
609 nmlib.notmuch_messages_destroy (self._msgs)
612 #------------------------------------------------------------------------------
613 class Message(object):
614 """Represents a single Email message
616 Technically, this wraps the underlying *notmuch_message_t* structure.
619 """notmuch_message_get_filename (notmuch_message_t *message)"""
620 _get_filename = nmlib.notmuch_message_get_filename
621 _get_filename.restype = c_char_p
622 """notmuch_message_get_message_id (notmuch_message_t *message)"""
623 _get_message_id = nmlib.notmuch_message_get_message_id
624 _get_message_id.restype = c_char_p
626 """notmuch_message_get_tags (notmuch_message_t *message)"""
627 _get_tags = nmlib.notmuch_message_get_tags
628 _get_tags.restype = c_void_p
630 _get_date = nmlib.notmuch_message_get_date
631 _get_date.restype = c_uint64
633 _get_header = nmlib.notmuch_message_get_header
634 _get_header.restype = c_char_p
636 def __init__(self, msg_p, parent=None):
638 :param msg_p: A pointer to an internal notmuch_message_t
639 Structure. If it is `None`, we will raise an :exc:`NotmuchError`
641 :param parent: A 'parent' object is passed which this message is
642 derived from. We save a reference to it, so we can
643 automatically delete the parent object once all derived
647 NotmuchError(STATUS.NULL_POINTER)
649 #keep reference to parent, so we keep it alive
650 self._parent = parent
651 logging.debug("Inited Message derived from %s" %(str(parent)))
654 def get_message_id(self):
655 """Return the message ID
657 :returns: String with a message ID
658 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
661 if self._msg is None:
662 raise NotmuchError(STATUS.NOT_INITIALIZED)
663 return Message._get_message_id(self._msg)
666 """Returns time_t of the message date
668 For the original textual representation of the Date header from the
669 message call notmuch_message_get_header() with a header value of
672 :returns: a time_t timestamp
674 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
677 if self._msg is None:
678 raise NotmuchError(STATUS.NOT_INITIALIZED)
679 return Message._get_date(self._msg)
681 def get_header(self, header):
682 """Returns a message header
684 This returns any message header that is stored in the notmuch database.
685 This is only a selected subset of headers, which is currently:
687 TODO: add stored headers
689 :param header: The name of the header to be retrieved.
690 It is not case-sensitive (TODO: confirm).
692 :returns: The header value as string
693 :exception: :exc:`NotmuchError`
695 * STATUS.NOT_INITIALIZED if the message
697 * STATUS.NULL_POINTER, if no header was found
699 if self._msg is None:
700 raise NotmuchError(STATUS.NOT_INITIALIZED)
702 #Returns NULL if any error occurs.
703 header = Message._get_header (self._msg, header)
705 raise NotmuchError(STATUS.NULL_POINTER)
708 def get_filename(self):
709 """Return the file path of the message file
711 :returns: Absolute file path & name of the message file
712 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
715 if self._msg is None:
716 raise NotmuchError(STATUS.NOT_INITIALIZED)
717 return Message._get_filename(self._msg)
720 """ Return the message tags
722 :returns: Message tags
723 :rtype: :class:`Tags`
724 :exception: :exc:`NotmuchError`
726 * STATUS.NOT_INITIALIZED if the message
728 * STATUS.NULL_POINTER, on error
730 if self._msg is None:
731 raise NotmuchError(STATUS.NOT_INITIALIZED)
733 tags_p = Message._get_tags(self._msg)
735 raise NotmuchError(STATUS.NULL_POINTER)
736 return Tags(tags_p, self)
738 def add_tag(self, tag):
739 """Add a tag to the given message
741 Adds a tag to the current message. The maximal tag length is defined in
742 the notmuch library and is currently 200 bytes.
744 :param tag: String with a 'tag' to be added.
745 :returns: STATUS.SUCCESS if the tag was successfully added.
746 Raises an exception otherwise.
747 :exception: :exc:`NotmuchError`. They have the following meaning:
750 The 'tag' argument is NULL
752 The length of 'tag' is too long
753 (exceeds Message.NOTMUCH_TAG_MAX)
754 STATUS.READ_ONLY_DATABASE
755 Database was opened in read-only mode so message cannot be
757 STATUS.NOT_INITIALIZED
758 The message has not been initialized.
760 if self._msg is None:
761 raise NotmuchError(STATUS.NOT_INITIALIZED)
763 status = nmlib.notmuch_message_add_tag (self._msg, tag)
765 if STATUS.SUCCESS == status:
769 raise NotmuchError(status)
771 def remove_tag(self, tag):
772 """Removes a tag from the given message
774 If the message has no such tag, this is a non-operation and
775 will report success anyway.
777 :param tag: String with a 'tag' to be removed.
778 :returns: STATUS.SUCCESS if the tag was successfully removed or if
779 the message had no such tag.
780 Raises an exception otherwise.
781 :exception: :exc:`NotmuchError`. They have the following meaning:
784 The 'tag' argument is NULL
786 The length of 'tag' is too long
787 (exceeds NOTMUCH_TAG_MAX)
788 STATUS.READ_ONLY_DATABASE
789 Database was opened in read-only mode so message cannot
791 STATUS.NOT_INITIALIZED
792 The message has not been initialized.
794 if self._msg is None:
795 raise NotmuchError(STATUS.NOT_INITIALIZED)
797 status = nmlib.notmuch_message_remove_tag(self._msg, tag)
799 if STATUS.SUCCESS == status:
803 raise NotmuchError(status)
805 def remove_all_tags(self):
806 """Removes all tags from the given message.
808 See :meth:`freeze` for an example showing how to safely
811 :returns: STATUS.SUCCESS if the tags were successfully removed.
812 Raises an exception otherwise.
813 :exception: :exc:`NotmuchError`. They have the following meaning:
815 STATUS.READ_ONLY_DATABASE
816 Database was opened in read-only mode so message cannot
818 STATUS.NOT_INITIALIZED
819 The message has not been initialized.
821 if self._msg is None:
822 raise NotmuchError(STATUS.NOT_INITIALIZED)
824 status = nmlib.notmuch_message_remove_all_tags(self._msg)
826 if STATUS.SUCCESS == status:
830 raise NotmuchError(status)
833 """Freezes the current state of 'message' within the database
835 This means that changes to the message state, (via :meth:`add_tag`,
836 :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
837 committed to the database until the message is :meth:`thaw`ed.
839 Multiple calls to freeze/thaw are valid and these calls will
840 "stack". That is there must be as many calls to thaw as to freeze
841 before a message is actually thawed.
843 The ability to do freeze/thaw allows for safe transactions to
844 change tag values. For example, explicitly setting a message to
845 have a given set of tags might look like this::
848 msg.remove_all_tags()
853 With freeze/thaw used like this, the message in the database is
854 guaranteed to have either the full set of original tag values, or
855 the full set of new tag values, but nothing in between.
857 Imagine the example above without freeze/thaw and the operation
858 somehow getting interrupted. This could result in the message being
859 left with no tags if the interruption happened after
860 :meth:`remove_all_tags` but before :meth:`add_tag`.
862 :returns: STATUS.SUCCESS if the message was successfully frozen.
863 Raises an exception otherwise.
864 :exception: :exc:`NotmuchError`. They have the following meaning:
866 STATUS.READ_ONLY_DATABASE
867 Database was opened in read-only mode so message cannot
869 STATUS.NOT_INITIALIZED
870 The message has not been initialized.
872 if self._msg is None:
873 raise NotmuchError(STATUS.NOT_INITIALIZED)
875 status = nmlib.notmuch_message_freeze(self._msg)
877 if STATUS.SUCCESS == status:
881 raise NotmuchError(status)
884 """Thaws the current 'message'
886 Thaw the current 'message', synchronizing any changes that may have
887 occurred while 'message' was frozen into the notmuch database.
889 See :meth:`freeze` for an example of how to use this
890 function to safely provide tag changes.
892 Multiple calls to freeze/thaw are valid and these calls with
893 "stack". That is there must be as many calls to thaw as to freeze
894 before a message is actually thawed.
896 :returns: STATUS.SUCCESS if the message was successfully frozen.
897 Raises an exception otherwise.
898 :exception: :exc:`NotmuchError`. They have the following meaning:
900 STATUS.UNBALANCED_FREEZE_THAW
901 An attempt was made to thaw an unfrozen message.
902 That is, there have been an unbalanced number of calls
903 to :meth:`freeze` and :meth:`thaw`.
904 STATUS.NOT_INITIALIZED
905 The message has not been initialized.
907 if self._msg is None:
908 raise NotmuchError(STATUS.NOT_INITIALIZED)
910 status = nmlib.notmuch_message_thaw(self._msg)
912 if STATUS.SUCCESS == status:
916 raise NotmuchError(status)
920 """A message() is represented by a 1-line summary"""
922 msg['from'] = self.get_header('from')
923 msg['tags'] = str(self.get_tags())
924 msg['date'] = date.fromtimestamp(self.get_date())
925 return "%(from)s (%(date)s) (%(tags)s)" % (msg)
927 def format_as_text(self):
928 """Output like notmuch show (Not implemented)"""
932 """Close and free the notmuch Message"""
933 if self._msg is not None:
934 logging.debug("Freeing the Message now")
935 nmlib.notmuch_message_destroy (self._msg)