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