f611777cafbc869cbf3addc6f7e558f0f84c719a
[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 `notmuch_database_t` or `None`
231
232         This should normally not be needed by a user (and is not yet
233         guaranteed to remain stable in future versions).
234         """
235         return self._db
236
237 #------------------------------------------------------------------------------
238 class Query(object):
239     """Represents a search query on an opened :class:`Database`.
240
241     A query selects and filters a subset of messages from the notmuch
242     database we derive from.
243
244     Technically, it wraps the underlying *notmuch_query_t* struct.
245
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.
251     """
252     # constants
253     SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
254     """Constants: Sort order in which to return results"""
255
256     """notmuch_query_create"""
257     _create = nmlib.notmuch_query_create
258     _create.restype = c_void_p
259
260     """notmuch_query_search_messages"""
261     _search_messages = nmlib.notmuch_query_search_messages
262     _search_messages.restype = c_void_p
263
264
265     """notmuch_query_count_messages"""
266     _count_messages = nmlib.notmuch_query_count_messages
267     _count_messages.restype = c_uint
268
269     def __init__(self, db, querystr):
270         """
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.
274         :type querystr: str
275         """
276         self._db = None
277         self._query = None
278         self.create(db, querystr)
279
280     def create(self, db, querystr):
281         """Creates a new query derived from a Database
282
283         This function is utilized by __init__() and usually does not need to 
284         be called directly.
285
286         :param db: Database to create the query from.
287         :type db: :class:`Database`
288         :param querystr: The query string
289         :type querystr: str
290         :returns: Nothing
291         :exception: :exc:`NotmuchError`
292
293                       * STATUS.NOT_INITIALIZED if db is not inited
294                       * STATUS.NULL_POINTER if the query creation failed 
295                         (too little memory)
296         """
297         if db.db_p is None:
298             raise NotmuchError(STATUS.NOT_INITIALIZED)            
299         # create reference to parent db to keep it alive
300         self._db = db
301         
302         # create query, return None if too little mem available
303         query_p = Query._create(db.db_p, querystr)
304         if query_p is None:
305             NotmuchError(STATUS.NULL_POINTER)
306         self._query = query_p
307
308     def set_sort(self, sort):
309         """Set the sort order future results will be delivered in
310
311         Wraps the underlying *notmuch_query_set_sort* function.
312
313         :param sort: Sort order (see :attr:`Query.SORT`)
314         :returns: Nothing
315         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not 
316                     been initialized.
317         """
318         if self._query is None:
319             raise NotmuchError(STATUS.NOT_INITIALIZED)
320
321         nmlib.notmuch_query_set_sort(self._query, sort)
322
323     def search_messages(self):
324         """Filter messages according to the query and return
325         :class:`Messages` in the defined sort order
326
327         Technically, it wraps the underlying
328         *notmuch_query_search_messages* function.
329
330         :returns: :class:`Messages`
331         :exception: :exc:`NotmuchError`
332
333                       * STATUS.NOT_INITIALIZED if query is not inited
334                       * STATUS.NULL_POINTER if search_messages failed 
335         """
336         if self._query is None:
337             raise NotmuchError(STATUS.NOT_INITIALIZED)            
338
339         msgs_p = Query._search_messages(self._query)
340
341         if msgs_p is None:
342             NotmuchError(STATUS.NULL_POINTER)
343
344         return Messages(msgs_p,self)
345
346     def count_messages(self):
347         """Estimate the number of messages matching the query
348
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.
355
356         :returns: :class:`Messages`
357         :exception: :exc:`NotmuchError`
358
359                       * STATUS.NOT_INITIALIZED if query is not inited
360         """
361         if self._query is None:
362             raise NotmuchError(STATUS.NOT_INITIALIZED)            
363
364         return Query._count_messages(self._query)
365
366     def __del__(self):
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)
371
372 #------------------------------------------------------------------------------
373 class Tags(object):
374     """Represents a list of notmuch tags
375
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::
383
384       for tag in tags: print tag 
385
386     as well as::
387
388        number_of_tags = len(tags)
389
390     and even a simple::
391
392        #str() iterates over all tags to construct a space separated list
393        print(str(tags))
394
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.
397     """
398
399     #notmuch_tags_get
400     _get = nmlib.notmuch_tags_get
401     _get.restype = c_char_p
402
403     def __init__(self, tags_p, parent=None):
404         """
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(?)
419         """
420         if tags_p is None:
421             NotmuchError(STATUS.NULL_POINTER)
422
423         self._tags = tags_p
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)))
427     
428     def __iter__(self):
429         """ Make Tags an iterator """
430         return self
431
432     def next(self):
433         if self._tags is None:
434             raise NotmuchError(STATUS.NOT_INITIALIZED)
435
436         if not nmlib.notmuch_tags_valid(self._tags):
437             self._tags = None
438             raise StopIteration
439
440         tag = Tags._get (self._tags)
441         nmlib.notmuch_tags_move_to_next(self._tags)
442         return tag
443
444     def __len__(self):
445         """len(:class:`Tags`) returns the number of contained tags
446
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
451                subsequent attempts.
452         """
453         if self._tags is None:
454             raise NotmuchError(STATUS.NOT_INITIALIZED)
455
456         i=0
457         while nmlib.notmuch_tags_valid(self._msgs):
458             nmlib.notmuch_tags_move_to_next(self._msgs)
459             i += 1
460         self._tags = None
461         return i
462
463     def __str__(self):
464         """The str() representation of Tags() is a space separated list of tags
465
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
470                subsequent attempts.
471         """
472         return " ".join(self)
473
474     def __del__(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)
479
480
481 #------------------------------------------------------------------------------
482 class Messages(object):
483     """Represents a list of notmuch messages
484
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::
494
495       for msg in msgs: print msg 
496
497     as well as::
498
499        number_of_msgs = len(msgs)
500
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.
503
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::
510
511       db   = Database()
512       msgs = Query(db,'').search_messages() #get a Messages() object
513       msglist = []
514       for m in msgs:
515          msglist.append(m)
516
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.
521       
522       # You can reiterate over *msglist* however as often as you want. 
523       # It is simply a list with Message objects.
524
525       print (msglist[0].get_filename())
526       print (msglist[1].get_filename())
527       print (msglist[0].get_message_id())
528     """
529
530     #notmuch_tags_get
531     _get = nmlib.notmuch_messages_get
532     _get.restype = c_void_p
533
534     _collect_tags = nmlib.notmuch_messages_collect_tags
535     _collect_tags.restype = c_void_p
536
537     def __init__(self, msgs_p, parent=None):
538         """
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.(?)
553         """
554         if msgs_p is None:
555             NotmuchError(STATUS.NULL_POINTER)
556
557         self._msgs = msgs_p
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)))
561
562     def collect_tags(self):
563         """Return the unique :class:`Tags` in the contained messages
564
565         :returns: :class:`Tags`
566         :exceptions: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if not inited
567
568         .. note:: :meth:`collect_tags` will iterate over the messages and
569           therefore will not allow further iterations.
570         """
571         if self._msgs is None:
572             raise NotmuchError(STATUS.NOT_INITIALIZED)
573
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
577         self._msgs = None
578
579         if tags_p == None:
580             raise NotmuchError(STATUS.NULL_POINTER)
581         return Tags(tags_p, self)
582
583     def __iter__(self):
584         """ Make Messages an iterator """
585         return self
586
587     def next(self):
588         if self._msgs is None:
589             raise NotmuchError(STATUS.NOT_INITIALIZED)
590
591         if not nmlib.notmuch_messages_valid(self._msgs):
592             self._msgs = None
593             raise StopIteration
594
595         msg = Message(Messages._get (self._msgs), self)
596         nmlib.notmuch_messages_move_to_next(self._msgs)
597         return msg
598
599     def __len__(self):
600         """len(:class:`Messages`) returns the number of contained messages
601
602         .. note:: As this iterates over the messages, we will not be able to 
603                iterate over them again (as in retrieve them)!
604         """
605         if self._msgs is None:
606             raise NotmuchError(STATUS.NOT_INITIALIZED)
607
608         i=0
609         while nmlib.notmuch_messages_valid(self._msgs):
610             nmlib.notmuch_messages_move_to_next(self._msgs)
611             i += 1
612         self._msgs = None
613         return i
614
615
616
617     def __del__(self):
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)
622
623
624 #------------------------------------------------------------------------------
625 class Message(object):
626     """Represents a single Email message
627
628     Technically, this wraps the underlying *notmuch_message_t* structure.
629     """
630
631     """notmuch_message_get_filename (notmuch_message_t *message)"""
632     _get_filename = nmlib.notmuch_message_get_filename
633     _get_filename.restype = c_char_p 
634
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 
638
639     """notmuch_message_get_thread_id"""
640     _get_thread_id = nmlib.notmuch_message_get_thread_id
641     _get_thread_id.restype = c_char_p
642
643     """notmuch_message_get_tags (notmuch_message_t *message)"""
644     _get_tags = nmlib.notmuch_message_get_tags
645     _get_tags.restype = c_void_p
646
647     _get_date = nmlib.notmuch_message_get_date
648     _get_date.restype = c_uint64
649
650     _get_header = nmlib.notmuch_message_get_header
651     _get_header.restype = c_char_p
652
653     def __init__(self, msg_p, parent=None):
654         """
655         :param msg_p: A pointer to an internal notmuch_message_t
656             Structure.  If it is `None`, we will raise an :exc:`NotmuchError`
657             STATUS.NULL_POINTER.
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
661               objects are dead.
662         """
663         if msg_p is None:
664             NotmuchError(STATUS.NULL_POINTER)
665         self._msg = msg_p
666         #keep reference to parent, so we keep it alive
667         self._parent = parent
668         logging.debug("Inited Message derived from %s" %(str(parent)))
669
670
671     def get_message_id(self):
672         """Returns the message ID
673         
674         :returns: String with a message ID
675         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
676                     is not initialized.
677         """
678         if self._msg is None:
679             raise NotmuchError(STATUS.NOT_INITIALIZED)
680         return Message._get_message_id(self._msg)
681
682     def get_thread_id(self):
683         """Returns the thread ID
684
685         The returned string belongs to 'message' will only be valid for as 
686         long as the message is valid.
687
688         This function will not return None since Notmuch ensures that every
689         message belongs to a single thread.
690
691         :returns: String with a thread ID
692         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
693                     is not initialized.
694         """
695         if self._msg is None:
696             raise NotmuchError(STATUS.NOT_INITIALIZED)
697
698         return Message._get_thread_id (self._msg);
699
700     def get_date(self):
701         """Returns time_t of the message date
702
703         For the original textual representation of the Date header from the
704         message call notmuch_message_get_header() with a header value of
705         "date".
706
707         :returns: a time_t timestamp
708         :rtype: c_unit64
709         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
710                     is not initialized.
711         """
712         if self._msg is None:
713             raise NotmuchError(STATUS.NOT_INITIALIZED)
714         return Message._get_date(self._msg)
715
716     def get_header(self, header):
717         """Returns a message header
718         
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:
721
722           TODO: add stored headers
723
724         :param header: The name of the header to be retrieved.
725                        It is not case-sensitive (TODO: confirm).
726         :type header: str
727         :returns: The header value as string
728         :exception: :exc:`NotmuchError`
729
730                     * STATUS.NOT_INITIALIZED if the message 
731                       is not initialized.
732                     * STATUS.NULL_POINTER, if no header was found
733         """
734         if self._msg is None:
735             raise NotmuchError(STATUS.NOT_INITIALIZED)
736
737         #Returns NULL if any error occurs.
738         header = Message._get_header (self._msg, header)
739         if header == None:
740             raise NotmuchError(STATUS.NULL_POINTER)
741         return header
742
743     def get_filename(self):
744         """Return the file path of the message file
745
746         :returns: Absolute file path & name of the message file
747         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if the message 
748               is not initialized.
749         """
750         if self._msg is None:
751             raise NotmuchError(STATUS.NOT_INITIALIZED)
752         return Message._get_filename(self._msg)
753
754     def get_tags(self):
755         """ Return the message tags
756
757         :returns: Message tags
758         :rtype: :class:`Tags`
759         :exception: :exc:`NotmuchError`
760
761                       * STATUS.NOT_INITIALIZED if the message 
762                         is not initialized.
763                       * STATUS.NULL_POINTER, on error
764         """
765         if self._msg is None:
766             raise NotmuchError(STATUS.NOT_INITIALIZED)
767
768         tags_p = Message._get_tags(self._msg)
769         if tags_p == None:
770             raise NotmuchError(STATUS.NULL_POINTER)
771         return Tags(tags_p, self)
772
773     def add_tag(self, tag):
774         """Add a tag to the given message
775
776         Adds a tag to the current message. The maximal tag length is defined in
777         the notmuch library and is currently 200 bytes.
778
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:
783
784                   STATUS.NULL_POINTER
785                     The 'tag' argument is NULL
786                   STATUS.TAG_TOO_LONG
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 
791                     modified.
792                   STATUS.NOT_INITIALIZED
793                      The message has not been initialized.
794        """
795         if self._msg is None:
796             raise NotmuchError(STATUS.NOT_INITIALIZED)
797
798         status = nmlib.notmuch_message_add_tag (self._msg, tag)
799
800         if STATUS.SUCCESS == status:
801             # return on success
802             return status
803
804         raise NotmuchError(status)
805
806     def remove_tag(self, tag):
807         """Removes a tag from the given message
808
809         If the message has no such tag, this is a non-operation and
810         will report success anyway.
811
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:
817
818                    STATUS.NULL_POINTER
819                      The 'tag' argument is NULL
820                    STATUS.TAG_TOO_LONG
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 
825                      be modified.
826                    STATUS.NOT_INITIALIZED
827                      The message has not been initialized.
828         """
829         if self._msg is None:
830             raise NotmuchError(STATUS.NOT_INITIALIZED)
831
832         status = nmlib.notmuch_message_remove_tag(self._msg, tag)
833
834         if STATUS.SUCCESS == status:
835             # return on success
836             return status
837
838         raise NotmuchError(status)
839
840     def remove_all_tags(self):
841         """Removes all tags from the given message.
842
843         See :meth:`freeze` for an example showing how to safely
844         replace tag values.
845
846         :returns: STATUS.SUCCESS if the tags were successfully removed.
847                   Raises an exception otherwise.
848         :exception: :exc:`NotmuchError`. They have the following meaning:
849
850                    STATUS.READ_ONLY_DATABASE
851                      Database was opened in read-only mode so message cannot 
852                      be modified.
853                    STATUS.NOT_INITIALIZED
854                      The message has not been initialized.
855         """
856         if self._msg is None:
857             raise NotmuchError(STATUS.NOT_INITIALIZED)
858  
859         status = nmlib.notmuch_message_remove_all_tags(self._msg)
860
861         if STATUS.SUCCESS == status:
862             # return on success
863             return status
864
865         raise NotmuchError(status)
866
867     def freeze(self):
868         """Freezes the current state of 'message' within the database
869
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.
873
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.
877
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::
881
882           msg.freeze()
883           msg.remove_all_tags()
884           for tag in new_tags:
885               msg.add_tag(tag)
886           msg.thaw()
887
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.
891
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`.
896
897         :returns: STATUS.SUCCESS if the message was successfully frozen.
898                   Raises an exception otherwise.
899         :exception: :exc:`NotmuchError`. They have the following meaning:
900
901                    STATUS.READ_ONLY_DATABASE
902                      Database was opened in read-only mode so message cannot 
903                      be modified.
904                    STATUS.NOT_INITIALIZED
905                      The message has not been initialized.
906         """
907         if self._msg is None:
908             raise NotmuchError(STATUS.NOT_INITIALIZED)
909  
910         status = nmlib.notmuch_message_freeze(self._msg)
911
912         if STATUS.SUCCESS == status:
913             # return on success
914             return status
915
916         raise NotmuchError(status)
917
918     def thaw(self):
919         """Thaws the current 'message'
920
921         Thaw the current 'message', synchronizing any changes that may have 
922         occurred while 'message' was frozen into the notmuch database.
923
924         See :meth:`freeze` for an example of how to use this
925         function to safely provide tag changes.
926
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.
930
931         :returns: STATUS.SUCCESS if the message was successfully frozen.
932                   Raises an exception otherwise.
933         :exception: :exc:`NotmuchError`. They have the following meaning:
934
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.
941         """
942         if self._msg is None:
943             raise NotmuchError(STATUS.NOT_INITIALIZED)
944  
945         status = nmlib.notmuch_message_thaw(self._msg)
946
947         if STATUS.SUCCESS == status:
948             # return on success
949             return status
950
951         raise NotmuchError(status)
952
953     
954     def __str__(self):
955         """A message() is represented by a 1-line summary"""
956         msg = {}
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)
961
962     def format_as_text(self):
963         """Output like notmuch show (Not implemented)"""
964         return str(self)
965
966     def __del__(self):
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)