]> git.notmuchmail.org Git - notmuch/blob - cnotmuch/database.py
Database(): Factor out the test for an initialized self._db
[notmuch] / cnotmuch / database.py
1 import ctypes, os
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
4 import logging
5 from datetime import date
6
7 class Database(object):
8     """Represents a notmuch database (wraps notmuch_database_t)
9
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.
15     """
16     _std_db_path = None
17     """Class attribute to cache user's default database"""
18
19     MODE = Enum(['READ_ONLY','READ_WRITE'])
20     """Constants: Mode in which to open the database"""
21
22     """notmuch_database_get_path (notmuch_database_t *database)"""
23     _get_path = nmlib.notmuch_database_get_path
24     _get_path.restype = c_char_p
25
26     """notmuch_database_get_version"""
27     _get_version = nmlib.notmuch_database_get_version
28     _get_version.restype = c_uint
29
30     """notmuch_database_open (const char *path, notmuch_database_mode_t mode)"""
31     _open = nmlib.notmuch_database_open 
32     _open.restype = c_void_p
33
34     """ notmuch_database_find_message """
35     _find_message = nmlib.notmuch_database_find_message
36     _find_message.restype = c_void_p
37
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
41
42     """ notmuch_database_create(const char *path):"""
43     _create = nmlib.notmuch_database_create
44     _create.restype = c_void_p
45
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`.
51
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.
54
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
59                        database.  
60         :type create:  bool
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`
64         :returns:      Nothing
65         :exception:    :exc:`NotmuchError` in case of failure.
66         """
67         self._db = None
68         if path is None:
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
74
75         if create == False:
76             self.open(path, mode)
77         else:
78             self.create(path)
79
80     def _verify_initialized_db(self):
81         """Raises a NotmuchError in case self._db is still None"""
82         if self._db is None:
83             raise NotmuchError(STATUS.NOT_INITIALIZED)            
84
85     def create(self, path):
86         """Creates a new notmuch database
87
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.
94
95         :param path: A directory in which we should create the database.
96         :type path: str
97         :returns: Nothing
98         :exception: :exc:`NotmuchError` in case of any failure
99                     (after printing an error message on stderr).
100         """
101         if self._db is not None:
102             raise NotmuchError(
103             message="Cannot create db, this Database() already has an open one.")
104
105         res = Database._create(path, Database.MODE.READ_WRITE)
106
107         if res is None:
108             raise NotmuchError(
109                 message="Could not create the specified database")
110         self._db = res
111
112     def open(self, path, mode= 0): 
113         """Opens an existing database
114
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.
118
119         :param status: Open the database in read-only or read-write mode
120         :type status:  :attr:`MODE` 
121         :returns: Nothing
122         :exception: Raises :exc:`NotmuchError` in case
123                     of any failure (after printing an error message on stderr).
124         """
125
126         res = Database._open(path, mode)
127
128         if res is None:
129             raise NotmuchError(
130                 message="Could not open the specified database")
131         self._db = res
132
133     def get_path(self):
134         """Returns the file path of an open database
135
136         Wraps notmuch_database_get_path"""
137         # Raise a NotmuchError if not initialized
138         self._verify_initialized_db()
139
140         return Database._get_path(self._db)
141
142     def get_version(self):
143         """Returns the database format version
144
145         :returns: The database version as positive integer
146         :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
147                     the database was not intitialized.
148         """
149         # Raise a NotmuchError if not initialized
150         self._verify_initialized_db()
151
152         return Database._get_version (self._db)
153
154     def needs_upgrade(self):
155         """Does this database need to be upgraded before writing to it?
156
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.
161
162         :returns: `True` or `False`
163         :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
164                     the database was not intitialized.
165         """
166         # Raise a NotmuchError if not initialized
167         self._verify_initialized_db()
168
169         return notmuch_database_needs_upgrade(self.db) 
170
171     def find_message(self, msgid):
172         """Returns a :class:`Message` as identified by its message ID
173
174         Wraps the underlying *notmuch_database_find_message* function.
175
176         :param msgid: The message ID
177         :type msgid: string
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.
182         """
183         # Raise a NotmuchError if not initialized
184         self._verify_initialized_db()
185
186         msg_p = Database._find_message(self._db, msgid)
187         if msg_p is None:
188             return None
189         return Message(msg_p, self)
190
191     def get_all_tags(self):
192         """Returns :class:`Tags` with a list of all tags found in the database
193
194         :returns: :class:`Tags`
195         :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
196         """
197         # Raise a NotmuchError if not initialized
198         self._verify_initialized_db()
199
200         tags_p = Database._get_all_tags (self._db)
201         if tags_p == None:
202             raise NotmuchError(STATUS.NULL_POINTER)
203         return Tags(tags_p, self)
204
205     def __repr__(self):
206         return "'Notmuch DB " + self.get_path() + "'"
207
208     def __del__(self):
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)
213
214     def _get_user_default_db(self):
215         """ Reads a user's notmuch config and returns his db location
216
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'))
222         config.read(conf_f)
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')
227
228     @property
229     def db_p(self):
230         """Property returning a pointer to the notmuch_database_t or `None`
231
232         This should normally not be needed by a user."""
233         return self._db
234
235 #------------------------------------------------------------------------------
236 class Query(object):
237     """ Represents a search query on an opened :class:`Database`.
238
239     A query selects and filters a subset of messages from the notmuch
240     database we derive from.
241
242     Technically, it wraps the underlying *notmuch_query_t* struct.
243
244     .. note:: Do remember that as soon as we tear down this object,
245            all underlying derived objects such as threads,
246            messages, tags etc will be freed by the underlying library
247            as well. Accessing these objects will lead to segfaults and
248            other unexpected behavior. See above for more details.
249     """
250     # constants
251     SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
252     """Constants: Sort order in which to return results"""
253
254     """notmuch_query_create"""
255     _create = nmlib.notmuch_query_create
256     _create.restype = c_void_p
257
258     """notmuch_query_search_messages"""
259     _search_messages = nmlib.notmuch_query_search_messages
260     _search_messages.restype = c_void_p
261
262
263     """notmuch_query_count_messages"""
264     _count_messages = nmlib.notmuch_query_count_messages
265     _count_messages.restype = c_uint
266
267     def __init__(self, db, querystr):
268         """
269         :param db: An open database which we derive the Query from.
270         :type db: :class:`Database`
271         :param querystr: The query string for the message.
272         :type querystr: str
273         """
274         self._db = None
275         self._query = None
276         self.create(db, querystr)
277
278     def create(self, db, querystr):
279         """Creates a new query derived from a Database.
280
281         This function is utilized by __init__() and usually does not need to 
282         be called directly.
283
284         :param db: Database to create the query from.
285         :type db: :class:`Database`
286         :param querystr: The query string
287         :type querystr: str
288         :returns: Nothing
289         :exception: :exc:`NotmuchError`
290
291                       * STATUS.NOT_INITIALIZED if db is not inited
292                       * STATUS.NULL_POINTER if the query creation failed 
293                         (too little memory)
294         """
295         if db.db_p is None:
296             raise NotmuchError(STATUS.NOT_INITIALIZED)            
297         # create reference to parent db to keep it alive
298         self._db = db
299         
300         # create query, return None if too little mem available
301         query_p = Query._create(db.db_p, querystr)
302         if query_p is None:
303             NotmuchError(STATUS.NULL_POINTER)
304         self._query = query_p
305
306     def set_sort(self, sort):
307         """Set the sort order future results will be delivered in
308
309         Wraps the underlying *notmuch_query_set_sort* function.
310
311         :param sort: Sort order (see :attr:`Query.SORT`)
312         :returns: Nothing
313         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not 
314                     been initialized.
315         """
316         if self._query is None:
317             raise NotmuchError(STATUS.NOT_INITIALIZED)
318
319         nmlib.notmuch_query_set_sort(self._query, sort)
320
321     def search_messages(self):
322         """Filter messages according to the query and return
323         :class:`Messages` in the defined sort order
324
325         Technically, it wraps the underlying
326         *notmuch_query_search_messages* function.
327
328         :returns: :class:`Messages`
329         :exception: :exc:`NotmuchError`
330
331                       * STATUS.NOT_INITIALIZED if query is not inited
332                       * STATUS.NULL_POINTER if search_messages failed 
333         """
334         if self._query is None:
335             raise NotmuchError(STATUS.NOT_INITIALIZED)            
336
337         msgs_p = Query._search_messages(self._query)
338
339         if msgs_p is None:
340             NotmuchError(STATUS.NULL_POINTER)
341
342         return Messages(msgs_p,self)
343
344     def count_messages(self):
345         """Estimate the number of messages matching the query
346
347         This function performs a search and returns Xapian's best
348         guess as to the number of matching messages. It is much faster
349         than performing :meth:`search_messages` and counting the
350         result with `len()` (although it always returned the same
351         result in my tests). Technically, it wraps the underlying
352         *notmuch_query_count_messages* function.
353
354         :returns: :class:`Messages`
355         :exception: :exc:`NotmuchError`
356
357                       * STATUS.NOT_INITIALIZED if query is not inited
358         """
359         if self._query is None:
360             raise NotmuchError(STATUS.NOT_INITIALIZED)            
361
362         return Query._count_messages(self._query)
363
364     def __del__(self):
365         """Close and free the Query"""
366         if self._query is not None:
367             logging.debug("Freeing the Query now")
368             nmlib.notmuch_query_destroy (self._query)
369
370 #------------------------------------------------------------------------------
371 class Tags(object):
372     """Represents a list of notmuch tags
373
374     This object provides an iterator over a list of notmuch tags. Do
375     note that the underlying library only provides a one-time iterator
376     (it cannot reset the iterator to the start). Thus iterating over
377     the function will "exhaust" the list of tags, and a subsequent
378     iteration attempt will raise a :exc:`NotmuchError`
379     STATUS.NOT_INITIALIZED. Also note, that any function that uses
380     iteration (nearly all) will also exhaust the tags. So both::
381
382       for tag in tags: print tag 
383
384     as well as::
385
386        number_of_tags = len(tags)
387
388     and even a simple::
389
390        #str() iterates over all tags to construct a space separated list
391        print(str(tags))
392
393     will "exhaust" the Tags. If you need to re-iterate over a list of
394     tags you will need to retrieve a new :class:`Tags` object.
395     """
396
397     #notmuch_tags_get
398     _get = nmlib.notmuch_tags_get
399     _get.restype = c_char_p
400
401     def __init__(self, tags_p, parent=None):
402         """
403         :param tags_p: A pointer to an underlying *notmuch_tags_t*
404              structure. These are not publically exposed, so a user
405              will almost never instantiate a :class:`Tags` object
406              herself. They are usually handed back as a result,
407              e.g. in :meth:`Database.get_all_tags`.  *tags_p* must be
408              valid, we will raise an :exc:`NotmuchError`
409              (STATUS.NULL_POINTER) if it is `None`.
410         :type tags_p: :class:`ctypes.c_void_p`
411         :param parent: The parent object (ie :class:`Database` or 
412              :class:`Message` these tags are derived from, and saves a
413              reference to it, so we can automatically delete the db object
414              once all derived objects are dead.
415         :TODO: Make the iterator optionally work more than once by
416                cache the tags in the Python object(?)
417         """
418         if tags_p is None:
419             NotmuchError(STATUS.NULL_POINTER)
420
421         self._tags = tags_p
422         #save reference to parent object so we keep it alive
423         self._parent = parent
424         logging.debug("Inited Tags derived from %s" %(repr(parent)))
425     
426     def __iter__(self):
427         """ Make Tags an iterator """
428         return self
429
430     def next(self):
431         if self._tags is None:
432             raise NotmuchError(STATUS.NOT_INITIALIZED)
433
434         if not nmlib.notmuch_tags_valid(self._tags):
435             self._tags = None
436             raise StopIteration
437
438         tag = Tags._get (self._tags)
439         nmlib.notmuch_tags_move_to_next(self._tags)
440         return tag
441
442     def __len__(self):
443         """len(:class:`Tags`) returns the number of contained tags
444
445         .. note:: As this iterates over the tags, we will not be able
446                to iterate over them again (as in retrieve them)! If
447                the tags have been exhausted already, this will raise a
448                :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
449                subsequent attempts.
450         """
451         if self._tags is None:
452             raise NotmuchError(STATUS.NOT_INITIALIZED)
453
454         i=0
455         while nmlib.notmuch_tags_valid(self._msgs):
456             nmlib.notmuch_tags_move_to_next(self._msgs)
457             i += 1
458         self._tags = None
459         return i
460
461     def __str__(self):
462         """The str() representation of Tags() is a space separated list of tags
463
464         .. note:: As this iterates over the tags, we will not be able
465                to iterate over them again (as in retrieve them)! If
466                the tags have been exhausted already, this will raise a
467                :exc:`NotmuchError` STATUS.NOT_INITIALIZED on
468                subsequent attempts.
469         """
470         return " ".join(self)
471
472     def __del__(self):
473         """Close and free the notmuch tags"""
474         if self._tags is not None:
475             logging.debug("Freeing the Tags now")
476             nmlib.notmuch_tags_destroy (self._tags)
477
478
479 #------------------------------------------------------------------------------
480 class Messages(object):
481     """Represents a list of notmuch messages
482
483     This object provides an iterator over a list of notmuch messages
484     (Technically, it provides a wrapper for the underlying
485     *notmuch_messages_t* structure). Do note that the underlying
486     library only provides a one-time iterator (it cannot reset the
487     iterator to the start). Thus iterating over the function will
488     "exhaust" the list of messages, and a subsequent iteration attempt
489     will raise a :exc:`NotmuchError` STATUS.NOT_INITIALIZED. Also
490     note, that any function that uses iteration will also
491     exhaust the messages. So both::
492
493       for msg in msgs: print msg 
494
495     as well as::
496
497        number_of_msgs = len(msgs)
498
499     will "exhaust" the Messages. If you need to re-iterate over a list of
500     messages you will need to retrieve a new :class:`Messages` object.
501
502     Things are not as bad as it seems though, you can store and reuse
503     the single Message objects as often as you want as long as you
504     keep the parent Messages object around. (Recall that due to
505     hierarchical memory allocation, all derived Message objects will
506     be invalid when we delete the parent Messages() object, even if it
507     was already "exhausted".) So this works::
508
509       db   = Database()
510       msgs = Query(db,'').search_messages() #get a Messages() object
511       msglist = []
512       for m in msgs:
513          msglist.append(m)
514
515       # msgs is "exhausted" now and even len(msgs) will raise an exception.
516       # However it will be kept around until all retrieved Message() objects are
517       # also deleted. If you did e.g. an explicit del(msgs) here, the 
518       # following lines would fail.
519       
520       # You can reiterate over *msglist* however as often as you want. 
521       # It is simply a list with Message objects.
522
523       print (msglist[0].get_filename())
524       print (msglist[1].get_filename())
525       print (msglist[0].get_message_id())
526     """
527
528     #notmuch_tags_get
529     _get = nmlib.notmuch_messages_get
530     _get.restype = c_void_p
531
532     _collect_tags = nmlib.notmuch_messages_collect_tags
533     _collect_tags.restype = c_void_p
534
535     def __init__(self, msgs_p, parent=None):
536         """
537         :param msgs_p:  A pointer to an underlying *notmuch_messages_t*
538              structure. These are not publically exposed, so a user
539              will almost never instantiate a :class:`Messages` object
540              herself. They are usually handed back as a result,
541              e.g. in :meth:`Query.search_messages`.  *msgs_p* must be
542              valid, we will raise an :exc:`NotmuchError`
543              (STATUS.NULL_POINTER) if it is `None`.
544         :type msgs_p: :class:`ctypes.c_void_p`
545         :param parent: The parent object
546              (ie :class:`Query`) these tags are derived from. It saves
547              a reference to it, so we can automatically delete the db
548              object once all derived objects are dead.
549         :TODO: Make the iterator work more than once and cache the tags in 
550                the Python object.(?)
551         """
552         if msgs_p is None:
553             NotmuchError(STATUS.NULL_POINTER)
554
555         self._msgs = msgs_p
556         #store parent, so we keep them alive as long as self  is alive
557         self._parent = parent
558         logging.debug("Inited Messages derived from %s" %(str(parent)))
559
560     def collect_tags(self):
561         """Return the unique :class:`Tags` in the contained messages
562
563         :returns: :class:`Tags`
564         :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
565
566         .. note:: :meth:`collect_tags` will iterate over the messages and
567           therefore will not allow further iterations.
568         """
569         if self._msgs is None:
570             raise NotmuchError(STATUS.NOT_INITIALIZED)
571
572         # collect all tags (returns NULL on error)
573         tags_p = Messages._collect_tags (self._msgs)
574         #reset _msgs as we iterated over it and can do so only once
575         self._msgs = None
576
577         if tags_p == None:
578             raise NotmuchError(STATUS.NULL_POINTER)
579         return Tags(tags_p, self)
580
581     def __iter__(self):
582         """ Make Messages an iterator """
583         return self
584
585     def next(self):
586         if self._msgs is None:
587             raise NotmuchError(STATUS.NOT_INITIALIZED)
588
589         if not nmlib.notmuch_messages_valid(self._msgs):
590             self._msgs = None
591             raise StopIteration
592
593         msg = Message(Messages._get (self._msgs), self)
594         nmlib.notmuch_messages_move_to_next(self._msgs)
595         return msg
596
597     def __len__(self):
598         """len(:class:`Messages`) returns the number of contained messages
599
600         .. note:: As this iterates over the messages, we will not be able to 
601                iterate over them again (as in retrieve them)!
602         """
603         if self._msgs is None:
604             raise NotmuchError(STATUS.NOT_INITIALIZED)
605
606         i=0
607         while nmlib.notmuch_messages_valid(self._msgs):
608             nmlib.notmuch_messages_move_to_next(self._msgs)
609             i += 1
610         self._msgs = None
611         return i
612
613
614
615     def __del__(self):
616         """Close and free the notmuch Messages"""
617         if self._msgs is not None:
618             logging.debug("Freeing the Messages now")
619             nmlib.notmuch_messages_destroy (self._msgs)
620
621
622 #------------------------------------------------------------------------------
623 class Message(object):
624     """Represents a single Email message
625
626     Technically, this wraps the underlying *notmuch_message_t* structure.
627     """
628
629     """notmuch_message_get_filename (notmuch_message_t *message)"""
630     _get_filename = nmlib.notmuch_message_get_filename
631     _get_filename.restype = c_char_p 
632
633     """notmuch_message_get_message_id (notmuch_message_t *message)"""
634     _get_message_id = nmlib.notmuch_message_get_message_id
635     _get_message_id.restype = c_char_p 
636
637     """notmuch_message_get_thread_id"""
638     _get_thread_id = nmlib.notmuch_message_get_thread_id
639     _get_thread_id.restype = c_char_p
640
641     """notmuch_message_get_tags (notmuch_message_t *message)"""
642     _get_tags = nmlib.notmuch_message_get_tags
643     _get_tags.restype = c_void_p
644
645     _get_date = nmlib.notmuch_message_get_date
646     _get_date.restype = c_uint64
647
648     _get_header = nmlib.notmuch_message_get_header
649     _get_header.restype = c_char_p
650
651     def __init__(self, msg_p, parent=None):
652         """
653         :param msg_p: A pointer to an internal notmuch_message_t
654             Structure.  If it is `None`, we will raise an :exc:`NotmuchError`
655             STATUS.NULL_POINTER.
656         :param parent: A 'parent' object is passed which this message is
657               derived from. We save a reference to it, so we can
658               automatically delete the parent object once all derived
659               objects are dead.
660         """
661         if msg_p is None:
662             NotmuchError(STATUS.NULL_POINTER)
663         self._msg = msg_p
664         #keep reference to parent, so we keep it alive
665         self._parent = parent
666         logging.debug("Inited Message derived from %s" %(str(parent)))
667
668
669     def get_message_id(self):
670         """Returns the message ID
671         
672         :returns: String with a message ID
673         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
674                     is not initialized.
675         """
676         if self._msg is None:
677             raise NotmuchError(STATUS.NOT_INITIALIZED)
678         return Message._get_message_id(self._msg)
679
680     def get_thread_id(self):
681         """Returns the thread ID
682
683         The returned string belongs to 'message' will only be valid for as 
684         long as the message is valid.
685
686         This function will not return None since Notmuch ensures that every
687         message belongs to a single thread.
688
689         :returns: String with a thread ID
690         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
691                     is not initialized.
692         """
693         if self._msg is None:
694             raise NotmuchError(STATUS.NOT_INITIALIZED)
695
696         return Message._get_thread_id (self._msg);
697
698     def get_date(self):
699         """Returns time_t of the message date
700
701         For the original textual representation of the Date header from the
702         message call notmuch_message_get_header() with a header value of
703         "date".
704
705         :returns: a time_t timestamp
706         :rtype: c_unit64
707         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
708                     is not initialized.
709         """
710         if self._msg is None:
711             raise NotmuchError(STATUS.NOT_INITIALIZED)
712         return Message._get_date(self._msg)
713
714     def get_header(self, header):
715         """Returns a message header
716         
717         This returns any message header that is stored in the notmuch database.
718         This is only a selected subset of headers, which is currently:
719
720           TODO: add stored headers
721
722         :param header: The name of the header to be retrieved.
723                        It is not case-sensitive (TODO: confirm).
724         :type header: str
725         :returns: The header value as string
726         :exception: :exc:`NotmuchError`
727
728                     * STATUS.NOT_INITIALIZED if the message 
729                       is not initialized.
730                     * STATUS.NULL_POINTER, if no header was found
731         """
732         if self._msg is None:
733             raise NotmuchError(STATUS.NOT_INITIALIZED)
734
735         #Returns NULL if any error occurs.
736         header = Message._get_header (self._msg, header)
737         if header == None:
738             raise NotmuchError(STATUS.NULL_POINTER)
739         return header
740
741     def get_filename(self):
742         """Return the file path of the message file
743
744         :returns: Absolute file path & name of the message file
745         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
746               is not initialized.
747         """
748         if self._msg is None:
749             raise NotmuchError(STATUS.NOT_INITIALIZED)
750         return Message._get_filename(self._msg)
751
752     def get_tags(self):
753         """ Return the message tags
754
755         :returns: Message tags
756         :rtype: :class:`Tags`
757         :exception: :exc:`NotmuchError`
758
759                       * STATUS.NOT_INITIALIZED if the message 
760                         is not initialized.
761                       * STATUS.NULL_POINTER, on error
762         """
763         if self._msg is None:
764             raise NotmuchError(STATUS.NOT_INITIALIZED)
765
766         tags_p = Message._get_tags(self._msg)
767         if tags_p == None:
768             raise NotmuchError(STATUS.NULL_POINTER)
769         return Tags(tags_p, self)
770
771     def add_tag(self, tag):
772         """Add a tag to the given message
773
774         Adds a tag to the current message. The maximal tag length is defined in
775         the notmuch library and is currently 200 bytes.
776
777         :param tag: String with a 'tag' to be added.
778         :returns: STATUS.SUCCESS if the tag was successfully added.
779                   Raises an exception otherwise.
780         :exception: :exc:`NotmuchError`. They have the following meaning:
781
782                   STATUS.NULL_POINTER
783                     The 'tag' argument is NULL
784                   STATUS.TAG_TOO_LONG
785                     The length of 'tag' is too long 
786                     (exceeds Message.NOTMUCH_TAG_MAX)
787                   STATUS.READ_ONLY_DATABASE
788                     Database was opened in read-only mode so message cannot be 
789                     modified.
790                   STATUS.NOT_INITIALIZED
791                      The message has not been initialized.
792        """
793         if self._msg is None:
794             raise NotmuchError(STATUS.NOT_INITIALIZED)
795
796         status = nmlib.notmuch_message_add_tag (self._msg, tag)
797
798         if STATUS.SUCCESS == status:
799             # return on success
800             return status
801
802         raise NotmuchError(status)
803
804     def remove_tag(self, tag):
805         """Removes a tag from the given message
806
807         If the message has no such tag, this is a non-operation and
808         will report success anyway.
809
810         :param tag: String with a 'tag' to be removed.
811         :returns: STATUS.SUCCESS if the tag was successfully removed or if 
812                   the message had no such tag.
813                   Raises an exception otherwise.
814         :exception: :exc:`NotmuchError`. They have the following meaning:
815
816                    STATUS.NULL_POINTER
817                      The 'tag' argument is NULL
818                    STATUS.TAG_TOO_LONG
819                      The length of 'tag' is too long
820                      (exceeds NOTMUCH_TAG_MAX)
821                    STATUS.READ_ONLY_DATABASE
822                      Database was opened in read-only mode so message cannot 
823                      be modified.
824                    STATUS.NOT_INITIALIZED
825                      The message has not been initialized.
826         """
827         if self._msg is None:
828             raise NotmuchError(STATUS.NOT_INITIALIZED)
829
830         status = nmlib.notmuch_message_remove_tag(self._msg, tag)
831
832         if STATUS.SUCCESS == status:
833             # return on success
834             return status
835
836         raise NotmuchError(status)
837
838     def remove_all_tags(self):
839         """Removes all tags from the given message.
840
841         See :meth:`freeze` for an example showing how to safely
842         replace tag values.
843
844         :returns: STATUS.SUCCESS if the tags were successfully removed.
845                   Raises an exception otherwise.
846         :exception: :exc:`NotmuchError`. They have the following meaning:
847
848                    STATUS.READ_ONLY_DATABASE
849                      Database was opened in read-only mode so message cannot 
850                      be modified.
851                    STATUS.NOT_INITIALIZED
852                      The message has not been initialized.
853         """
854         if self._msg is None:
855             raise NotmuchError(STATUS.NOT_INITIALIZED)
856  
857         status = nmlib.notmuch_message_remove_all_tags(self._msg)
858
859         if STATUS.SUCCESS == status:
860             # return on success
861             return status
862
863         raise NotmuchError(status)
864
865     def freeze(self):
866         """Freezes the current state of 'message' within the database
867
868         This means that changes to the message state, (via :meth:`add_tag`, 
869         :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be 
870         committed to the database until the message is :meth:`thaw`ed.
871
872         Multiple calls to freeze/thaw are valid and these calls will
873         "stack". That is there must be as many calls to thaw as to freeze
874         before a message is actually thawed.
875
876         The ability to do freeze/thaw allows for safe transactions to
877         change tag values. For example, explicitly setting a message to
878         have a given set of tags might look like this::
879
880           msg.freeze()
881           msg.remove_all_tags()
882           for tag in new_tags:
883               msg.add_tag(tag)
884           msg.thaw()
885
886         With freeze/thaw used like this, the message in the database is
887         guaranteed to have either the full set of original tag values, or
888         the full set of new tag values, but nothing in between.
889
890         Imagine the example above without freeze/thaw and the operation
891         somehow getting interrupted. This could result in the message being
892         left with no tags if the interruption happened after
893         :meth:`remove_all_tags` but before :meth:`add_tag`.
894
895         :returns: STATUS.SUCCESS if the message was successfully frozen.
896                   Raises an exception otherwise.
897         :exception: :exc:`NotmuchError`. They have the following meaning:
898
899                    STATUS.READ_ONLY_DATABASE
900                      Database was opened in read-only mode so message cannot 
901                      be modified.
902                    STATUS.NOT_INITIALIZED
903                      The message has not been initialized.
904         """
905         if self._msg is None:
906             raise NotmuchError(STATUS.NOT_INITIALIZED)
907  
908         status = nmlib.notmuch_message_freeze(self._msg)
909
910         if STATUS.SUCCESS == status:
911             # return on success
912             return status
913
914         raise NotmuchError(status)
915
916     def thaw(self):
917         """Thaws the current 'message'
918
919         Thaw the current 'message', synchronizing any changes that may have 
920         occurred while 'message' was frozen into the notmuch database.
921
922         See :meth:`freeze` for an example of how to use this
923         function to safely provide tag changes.
924
925         Multiple calls to freeze/thaw are valid and these calls with
926         "stack". That is there must be as many calls to thaw as to freeze
927         before a message is actually thawed.
928
929         :returns: STATUS.SUCCESS if the message was successfully frozen.
930                   Raises an exception otherwise.
931         :exception: :exc:`NotmuchError`. They have the following meaning:
932
933                    STATUS.UNBALANCED_FREEZE_THAW
934                      An attempt was made to thaw an unfrozen message. 
935                      That is, there have been an unbalanced number of calls 
936                      to :meth:`freeze` and :meth:`thaw`.
937                    STATUS.NOT_INITIALIZED
938                      The message has not been initialized.
939         """
940         if self._msg is None:
941             raise NotmuchError(STATUS.NOT_INITIALIZED)
942  
943         status = nmlib.notmuch_message_thaw(self._msg)
944
945         if STATUS.SUCCESS == status:
946             # return on success
947             return status
948
949         raise NotmuchError(status)
950
951     
952     def __str__(self):
953         """A message() is represented by a 1-line summary"""
954         msg = {}
955         msg['from'] = self.get_header('from')
956         msg['tags'] = str(self.get_tags())
957         msg['date'] = date.fromtimestamp(self.get_date())
958         return "%(from)s (%(date)s) (%(tags)s)" % (msg)
959
960     def format_as_text(self):
961         """Output like notmuch show (Not implemented)"""
962         return str(self)
963
964     def __del__(self):
965         """Close and free the notmuch Message"""
966         if self._msg is not None:
967             logging.debug("Freeing the Message now")
968             nmlib.notmuch_message_destroy (self._msg)