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 _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 find_message(self, msgid):
172 """Returns a :class:`Message` as identified by its message ID
174 Wraps the underlying *notmuch_database_find_message* function.
176 :param msgid: The message ID
178 :returns: :class:`Message` or `None` if no message is found or if an
179 out-of-memory situation occurs.
180 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
181 the database was not intitialized.
183 # Raise a NotmuchError if not initialized
184 self._verify_initialized_db()
186 msg_p = Database._find_message(self._db, msgid)
189 return Message(msg_p, self)
191 def get_all_tags(self):
192 """Returns :class:`Tags` with a list of all tags found in the database
194 :returns: :class:`Tags`
195 :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
197 # Raise a NotmuchError if not initialized
198 self._verify_initialized_db()
200 tags_p = Database._get_all_tags (self._db)
202 raise NotmuchError(STATUS.NULL_POINTER)
203 return Tags(tags_p, self)
206 return "'Notmuch DB " + self.get_path() + "'"
209 """Close and free the notmuch database if needed"""
210 if self._db is not None:
211 logging.debug("Freeing the database now")
212 nmlib.notmuch_database_close(self._db)
214 def _get_user_default_db(self):
215 """ Reads a user's notmuch config and returns his db location
217 Throws a NotmuchError if it cannot find it"""
218 from ConfigParser import SafeConfigParser
219 config = SafeConfigParser()
220 conf_f = os.getenv('NOTMUCH_CONFIG',
221 os.path.expanduser('~/.notmuch-config'))
223 if not config.has_option('database','path'):
224 raise NotmuchError(message=
225 "No DB path specified and no user default found")
226 return config.get('database','path')
230 """Property returning a pointer to `notmuch_database_t` or `None`
232 This should normally not be needed by a user (and is not yet
233 guaranteed to remain stable in future versions).
237 #------------------------------------------------------------------------------
239 """Represents a search query on an opened :class:`Database`.
241 A query selects and filters a subset of messages from the notmuch
242 database we derive from.
244 Technically, it wraps the underlying *notmuch_query_t* struct.
246 .. note:: Do remember that as soon as we tear down this object,
247 all underlying derived objects such as threads,
248 messages, tags etc will be freed by the underlying library
249 as well. Accessing these objects will lead to segfaults and
250 other unexpected behavior. See above for more details.
253 SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
254 """Constants: Sort order in which to return results"""
256 """notmuch_query_create"""
257 _create = nmlib.notmuch_query_create
258 _create.restype = c_void_p
260 """notmuch_query_search_messages"""
261 _search_messages = nmlib.notmuch_query_search_messages
262 _search_messages.restype = c_void_p
265 """notmuch_query_count_messages"""
266 _count_messages = nmlib.notmuch_query_count_messages
267 _count_messages.restype = c_uint
269 def __init__(self, db, querystr):
271 :param db: An open database which we derive the Query from.
272 :type db: :class:`Database`
273 :param querystr: The query string for the message.
278 self.create(db, querystr)
280 def create(self, db, querystr):
281 """Creates a new query derived from a Database
283 This function is utilized by __init__() and usually does not need to
286 :param db: Database to create the query from.
287 :type db: :class:`Database`
288 :param querystr: The query string
291 :exception: :exc:`NotmuchError`
293 * STATUS.NOT_INITIALIZED if db is not inited
294 * STATUS.NULL_POINTER if the query creation failed
298 raise NotmuchError(STATUS.NOT_INITIALIZED)
299 # create reference to parent db to keep it alive
302 # create query, return None if too little mem available
303 query_p = Query._create(db.db_p, querystr)
305 NotmuchError(STATUS.NULL_POINTER)
306 self._query = query_p
308 def set_sort(self, sort):
309 """Set the sort order future results will be delivered in
311 Wraps the underlying *notmuch_query_set_sort* function.
313 :param sort: Sort order (see :attr:`Query.SORT`)
315 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not
318 if self._query is None:
319 raise NotmuchError(STATUS.NOT_INITIALIZED)
321 nmlib.notmuch_query_set_sort(self._query, sort)
323 def search_messages(self):
324 """Filter messages according to the query and return
325 :class:`Messages` in the defined sort order
327 Technically, it wraps the underlying
328 *notmuch_query_search_messages* function.
330 :returns: :class:`Messages`
331 :exception: :exc:`NotmuchError`
333 * STATUS.NOT_INITIALIZED if query is not inited
334 * STATUS.NULL_POINTER if search_messages failed
336 if self._query is None:
337 raise NotmuchError(STATUS.NOT_INITIALIZED)
339 msgs_p = Query._search_messages(self._query)
342 NotmuchError(STATUS.NULL_POINTER)
344 return Messages(msgs_p,self)
346 def count_messages(self):
347 """Estimate the number of messages matching the query
349 This function performs a search and returns Xapian's best
350 guess as to the number of matching messages. It is much faster
351 than performing :meth:`search_messages` and counting the
352 result with `len()` (although it always returned the same
353 result in my tests). Technically, it wraps the underlying
354 *notmuch_query_count_messages* function.
356 :returns: :class:`Messages`
357 :exception: :exc:`NotmuchError`
359 * STATUS.NOT_INITIALIZED if query is not inited
361 if self._query is None:
362 raise NotmuchError(STATUS.NOT_INITIALIZED)
364 return Query._count_messages(self._query)
367 """Close and free the Query"""
368 if self._query is not None:
369 logging.debug("Freeing the Query now")
370 nmlib.notmuch_query_destroy (self._query)
372 #------------------------------------------------------------------------------
374 """Represents a list of notmuch tags
376 This object provides an iterator over a list of notmuch tags. Do
377 note that the underlying library only provides a one-time iterator
378 (it cannot reset the iterator to the start). Thus iterating over
379 the function will "exhaust" the list of tags, and a subsequent
380 iteration attempt will raise a :exc:`NotmuchError`
381 STATUS.NOT_INITIALIZED. Also note, that any function that uses
382 iteration (nearly all) will also exhaust the tags. So both::
384 for tag in tags: print tag
388 number_of_tags = len(tags)
392 #str() iterates over all tags to construct a space separated list
395 will "exhaust" the Tags. If you need to re-iterate over a list of
396 tags you will need to retrieve a new :class:`Tags` object.
400 _get = nmlib.notmuch_tags_get
401 _get.restype = c_char_p
403 def __init__(self, tags_p, parent=None):
405 :param tags_p: A pointer to an underlying *notmuch_tags_t*
406 structure. These are not publically exposed, so a user
407 will almost never instantiate a :class:`Tags` object
408 herself. They are usually handed back as a result,
409 e.g. in :meth:`Database.get_all_tags`. *tags_p* must be
410 valid, we will raise an :exc:`NotmuchError`
411 (STATUS.NULL_POINTER) if it is `None`.
412 :type tags_p: :class:`ctypes.c_void_p`
413 :param parent: The parent object (ie :class:`Database` or
414 :class:`Message` these tags are derived from, and saves a
415 reference to it, so we can automatically delete the db object
416 once all derived objects are dead.
417 :TODO: Make the iterator optionally work more than once by
418 cache the tags in the Python object(?)
421 NotmuchError(STATUS.NULL_POINTER)
424 #save reference to parent object so we keep it alive
425 self._parent = parent
426 logging.debug("Inited Tags derived from %s" %(repr(parent)))
429 """ Make Tags an iterator """
433 if self._tags is None:
434 raise NotmuchError(STATUS.NOT_INITIALIZED)
436 if not nmlib.notmuch_tags_valid(self._tags):
440 tag = Tags._get (self._tags)
441 nmlib.notmuch_tags_move_to_next(self._tags)
445 """len(:class:`Tags`) returns the number of contained tags
447 .. note:: As this iterates over the tags, we will not be able
448 to iterate over them again (as in retrieve them)! If
449 the tags have been exhausted already, this will raise a
450 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
453 if self._tags is None:
454 raise NotmuchError(STATUS.NOT_INITIALIZED)
457 while nmlib.notmuch_tags_valid(self._msgs):
458 nmlib.notmuch_tags_move_to_next(self._msgs)
464 """The str() representation of Tags() is a space separated list of tags
466 .. note:: As this iterates over the tags, we will not be able
467 to iterate over them again (as in retrieve them)! If
468 the tags have been exhausted already, this will raise a
469 :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
472 return " ".join(self)
475 """Close and free the notmuch tags"""
476 if self._tags is not None:
477 logging.debug("Freeing the Tags now")
478 nmlib.notmuch_tags_destroy (self._tags)
481 #------------------------------------------------------------------------------
482 class Messages(object):
483 """Represents a list of notmuch messages
485 This object provides an iterator over a list of notmuch messages
486 (Technically, it provides a wrapper for the underlying
487 *notmuch_messages_t* structure). Do note that the underlying
488 library only provides a one-time iterator (it cannot reset the
489 iterator to the start). Thus iterating over the function will
490 "exhaust" the list of messages, and a subsequent iteration attempt
491 will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
492 note, that any function that uses iteration will also
493 exhaust the messages. So both::
495 for msg in msgs: print msg
499 number_of_msgs = len(msgs)
501 will "exhaust" the Messages. If you need to re-iterate over a list of
502 messages you will need to retrieve a new :class:`Messages` object.
504 Things are not as bad as it seems though, you can store and reuse
505 the single Message objects as often as you want as long as you
506 keep the parent Messages object around. (Recall that due to
507 hierarchical memory allocation, all derived Message objects will
508 be invalid when we delete the parent Messages() object, even if it
509 was already "exhausted".) So this works::
512 msgs = Query(db,'').search_messages() #get a Messages() object
517 # msgs is "exhausted" now and even len(msgs) will raise an exception.
518 # However it will be kept around until all retrieved Message() objects are
519 # also deleted. If you did e.g. an explicit del(msgs) here, the
520 # following lines would fail.
522 # You can reiterate over *msglist* however as often as you want.
523 # It is simply a list with Message objects.
525 print (msglist[0].get_filename())
526 print (msglist[1].get_filename())
527 print (msglist[0].get_message_id())
531 _get = nmlib.notmuch_messages_get
532 _get.restype = c_void_p
534 _collect_tags = nmlib.notmuch_messages_collect_tags
535 _collect_tags.restype = c_void_p
537 def __init__(self, msgs_p, parent=None):
539 :param msgs_p: A pointer to an underlying *notmuch_messages_t*
540 structure. These are not publically exposed, so a user
541 will almost never instantiate a :class:`Messages` object
542 herself. They are usually handed back as a result,
543 e.g. in :meth:`Query.search_messages`. *msgs_p* must be
544 valid, we will raise an :exc:`NotmuchError`
545 (STATUS.NULL_POINTER) if it is `None`.
546 :type msgs_p: :class:`ctypes.c_void_p`
547 :param parent: The parent object
548 (ie :class:`Query`) these tags are derived from. It saves
549 a reference to it, so we can automatically delete the db
550 object once all derived objects are dead.
551 :TODO: Make the iterator work more than once and cache the tags in
552 the Python object.(?)
555 NotmuchError(STATUS.NULL_POINTER)
558 #store parent, so we keep them alive as long as self is alive
559 self._parent = parent
560 logging.debug("Inited Messages derived from %s" %(str(parent)))
562 def collect_tags(self):
563 """Return the unique :class:`Tags` in the contained messages
565 :returns: :class:`Tags`
566 :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
568 .. note:: :meth:`collect_tags` will iterate over the messages and
569 therefore will not allow further iterations.
571 if self._msgs is None:
572 raise NotmuchError(STATUS.NOT_INITIALIZED)
574 # collect all tags (returns NULL on error)
575 tags_p = Messages._collect_tags (self._msgs)
576 #reset _msgs as we iterated over it and can do so only once
580 raise NotmuchError(STATUS.NULL_POINTER)
581 return Tags(tags_p, self)
584 """ Make Messages an iterator """
588 if self._msgs is None:
589 raise NotmuchError(STATUS.NOT_INITIALIZED)
591 if not nmlib.notmuch_messages_valid(self._msgs):
595 msg = Message(Messages._get (self._msgs), self)
596 nmlib.notmuch_messages_move_to_next(self._msgs)
600 """len(:class:`Messages`) returns the number of contained messages
602 .. note:: As this iterates over the messages, we will not be able to
603 iterate over them again (as in retrieve them)!
605 if self._msgs is None:
606 raise NotmuchError(STATUS.NOT_INITIALIZED)
609 while nmlib.notmuch_messages_valid(self._msgs):
610 nmlib.notmuch_messages_move_to_next(self._msgs)
618 """Close and free the notmuch Messages"""
619 if self._msgs is not None:
620 logging.debug("Freeing the Messages now")
621 nmlib.notmuch_messages_destroy (self._msgs)
624 #------------------------------------------------------------------------------
625 class Message(object):
626 """Represents a single Email message
628 Technically, this wraps the underlying *notmuch_message_t* structure.
631 """notmuch_message_get_filename (notmuch_message_t *message)"""
632 _get_filename = nmlib.notmuch_message_get_filename
633 _get_filename.restype = c_char_p
635 """notmuch_message_get_message_id (notmuch_message_t *message)"""
636 _get_message_id = nmlib.notmuch_message_get_message_id
637 _get_message_id.restype = c_char_p
639 """notmuch_message_get_thread_id"""
640 _get_thread_id = nmlib.notmuch_message_get_thread_id
641 _get_thread_id.restype = c_char_p
643 """notmuch_message_get_tags (notmuch_message_t *message)"""
644 _get_tags = nmlib.notmuch_message_get_tags
645 _get_tags.restype = c_void_p
647 _get_date = nmlib.notmuch_message_get_date
648 _get_date.restype = c_uint64
650 _get_header = nmlib.notmuch_message_get_header
651 _get_header.restype = c_char_p
653 def __init__(self, msg_p, parent=None):
655 :param msg_p: A pointer to an internal notmuch_message_t
656 Structure. If it is `None`, we will raise an :exc:`NotmuchError`
658 :param parent: A 'parent' object is passed which this message is
659 derived from. We save a reference to it, so we can
660 automatically delete the parent object once all derived
664 NotmuchError(STATUS.NULL_POINTER)
666 #keep reference to parent, so we keep it alive
667 self._parent = parent
668 logging.debug("Inited Message derived from %s" %(str(parent)))
671 def get_message_id(self):
672 """Returns the message ID
674 :returns: String with a message ID
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_message_id(self._msg)
682 def get_thread_id(self):
683 """Returns the thread ID
685 The returned string belongs to 'message' will only be valid for as
686 long as the message is valid.
688 This function will not return None since Notmuch ensures that every
689 message belongs to a single thread.
691 :returns: String with a thread ID
692 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
695 if self._msg is None:
696 raise NotmuchError(STATUS.NOT_INITIALIZED)
698 return Message._get_thread_id (self._msg);
701 """Returns time_t of the message date
703 For the original textual representation of the Date header from the
704 message call notmuch_message_get_header() with a header value of
707 :returns: a time_t timestamp
709 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
712 if self._msg is None:
713 raise NotmuchError(STATUS.NOT_INITIALIZED)
714 return Message._get_date(self._msg)
716 def get_header(self, header):
717 """Returns a message header
719 This returns any message header that is stored in the notmuch database.
720 This is only a selected subset of headers, which is currently:
722 TODO: add stored headers
724 :param header: The name of the header to be retrieved.
725 It is not case-sensitive (TODO: confirm).
727 :returns: The header value as string
728 :exception: :exc:`NotmuchError`
730 * STATUS.NOT_INITIALIZED if the message
732 * STATUS.NULL_POINTER, if no header was found
734 if self._msg is None:
735 raise NotmuchError(STATUS.NOT_INITIALIZED)
737 #Returns NULL if any error occurs.
738 header = Message._get_header (self._msg, header)
740 raise NotmuchError(STATUS.NULL_POINTER)
743 def get_filename(self):
744 """Return the file path of the message file
746 :returns: Absolute file path & name of the message file
747 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message
750 if self._msg is None:
751 raise NotmuchError(STATUS.NOT_INITIALIZED)
752 return Message._get_filename(self._msg)
755 """ Return the message tags
757 :returns: Message tags
758 :rtype: :class:`Tags`
759 :exception: :exc:`NotmuchError`
761 * STATUS.NOT_INITIALIZED if the message
763 * STATUS.NULL_POINTER, on error
765 if self._msg is None:
766 raise NotmuchError(STATUS.NOT_INITIALIZED)
768 tags_p = Message._get_tags(self._msg)
770 raise NotmuchError(STATUS.NULL_POINTER)
771 return Tags(tags_p, self)
773 def add_tag(self, tag):
774 """Add a tag to the given message
776 Adds a tag to the current message. The maximal tag length is defined in
777 the notmuch library and is currently 200 bytes.
779 :param tag: String with a 'tag' to be added.
780 :returns: STATUS.SUCCESS if the tag was successfully added.
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 Message.NOTMUCH_TAG_MAX)
789 STATUS.READ_ONLY_DATABASE
790 Database was opened in read-only mode so message cannot be
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_add_tag (self._msg, tag)
800 if STATUS.SUCCESS == status:
804 raise NotmuchError(status)
806 def remove_tag(self, tag):
807 """Removes a tag from the given message
809 If the message has no such tag, this is a non-operation and
810 will report success anyway.
812 :param tag: String with a 'tag' to be removed.
813 :returns: STATUS.SUCCESS if the tag was successfully removed or if
814 the message had no such tag.
815 Raises an exception otherwise.
816 :exception: :exc:`NotmuchError`. They have the following meaning:
819 The 'tag' argument is NULL
821 The length of 'tag' is too long
822 (exceeds NOTMUCH_TAG_MAX)
823 STATUS.READ_ONLY_DATABASE
824 Database was opened in read-only mode so message cannot
826 STATUS.NOT_INITIALIZED
827 The message has not been initialized.
829 if self._msg is None:
830 raise NotmuchError(STATUS.NOT_INITIALIZED)
832 status = nmlib.notmuch_message_remove_tag(self._msg, tag)
834 if STATUS.SUCCESS == status:
838 raise NotmuchError(status)
840 def remove_all_tags(self):
841 """Removes all tags from the given message.
843 See :meth:`freeze` for an example showing how to safely
846 :returns: STATUS.SUCCESS if the tags were successfully removed.
847 Raises an exception otherwise.
848 :exception: :exc:`NotmuchError`. They have the following meaning:
850 STATUS.READ_ONLY_DATABASE
851 Database was opened in read-only mode so message cannot
853 STATUS.NOT_INITIALIZED
854 The message has not been initialized.
856 if self._msg is None:
857 raise NotmuchError(STATUS.NOT_INITIALIZED)
859 status = nmlib.notmuch_message_remove_all_tags(self._msg)
861 if STATUS.SUCCESS == status:
865 raise NotmuchError(status)
868 """Freezes the current state of 'message' within the database
870 This means that changes to the message state, (via :meth:`add_tag`,
871 :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
872 committed to the database until the message is :meth:`thaw`ed.
874 Multiple calls to freeze/thaw are valid and these calls will
875 "stack". That is there must be as many calls to thaw as to freeze
876 before a message is actually thawed.
878 The ability to do freeze/thaw allows for safe transactions to
879 change tag values. For example, explicitly setting a message to
880 have a given set of tags might look like this::
883 msg.remove_all_tags()
888 With freeze/thaw used like this, the message in the database is
889 guaranteed to have either the full set of original tag values, or
890 the full set of new tag values, but nothing in between.
892 Imagine the example above without freeze/thaw and the operation
893 somehow getting interrupted. This could result in the message being
894 left with no tags if the interruption happened after
895 :meth:`remove_all_tags` but before :meth:`add_tag`.
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.READ_ONLY_DATABASE
902 Database was opened in read-only mode so message cannot
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_freeze(self._msg)
912 if STATUS.SUCCESS == status:
916 raise NotmuchError(status)
919 """Thaws the current 'message'
921 Thaw the current 'message', synchronizing any changes that may have
922 occurred while 'message' was frozen into the notmuch database.
924 See :meth:`freeze` for an example of how to use this
925 function to safely provide tag changes.
927 Multiple calls to freeze/thaw are valid and these calls with
928 "stack". That is there must be as many calls to thaw as to freeze
929 before a message is actually thawed.
931 :returns: STATUS.SUCCESS if the message was successfully frozen.
932 Raises an exception otherwise.
933 :exception: :exc:`NotmuchError`. They have the following meaning:
935 STATUS.UNBALANCED_FREEZE_THAW
936 An attempt was made to thaw an unfrozen message.
937 That is, there have been an unbalanced number of calls
938 to :meth:`freeze` and :meth:`thaw`.
939 STATUS.NOT_INITIALIZED
940 The message has not been initialized.
942 if self._msg is None:
943 raise NotmuchError(STATUS.NOT_INITIALIZED)
945 status = nmlib.notmuch_message_thaw(self._msg)
947 if STATUS.SUCCESS == status:
951 raise NotmuchError(status)
955 """A message() is represented by a 1-line summary"""
957 msg['from'] = self.get_header('from')
958 msg['tags'] = str(self.get_tags())
959 msg['date'] = date.fromtimestamp(self.get_date())
960 return "%(from)s (%(date)s) (%(tags)s)" % (msg)
962 def format_as_text(self):
963 """Output like notmuch show (Not implemented)"""
967 """Close and free the notmuch Message"""
968 if self._msg is not None:
969 logging.debug("Freeing the Message now")
970 nmlib.notmuch_message_destroy (self._msg)