docs: Improve documentations
[notmuch] / cnotmuch / database.py
1 import os
2 from ctypes import c_int, c_char_p, c_void_p, c_uint, c_long, byref
3 from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
4 from cnotmuch.thread import Threads
5 from cnotmuch.message import Messages
6 from cnotmuch.tag import Tags
7
8 class Database(object):
9     """Represents a notmuch database (wraps notmuch_database_t)
10
11     .. note:: Do remember that as soon as we tear down this object,
12            all underlying derived objects such as queries, threads,
13            messages, tags etc will be freed by the underlying library
14            as well. Accessing these objects will lead to segfaults and
15            other unexpected behavior. See above for more details.
16     """
17     _std_db_path = None
18     """Class attribute to cache user's default database"""
19
20     MODE = Enum(['READ_ONLY','READ_WRITE'])
21     """Constants: Mode in which to open the database"""
22
23     """notmuch_database_get_directory"""
24     _get_directory = nmlib.notmuch_database_get_directory
25     _get_directory.restype = c_void_p
26
27     """notmuch_database_get_path"""
28     _get_path = nmlib.notmuch_database_get_path
29     _get_path.restype = c_char_p
30
31     """notmuch_database_get_version"""
32     _get_version = nmlib.notmuch_database_get_version
33     _get_version.restype = c_uint
34
35     """notmuch_database_open"""
36     _open = nmlib.notmuch_database_open 
37     _open.restype = c_void_p
38
39     """notmuch_database_upgrade"""
40     _upgrade = nmlib.notmuch_database_upgrade
41     _upgrade.argtypes = [c_void_p, c_void_p, c_void_p]
42
43     """ notmuch_database_find_message"""
44     _find_message = nmlib.notmuch_database_find_message
45     _find_message.restype = c_void_p
46
47     """notmuch_database_get_all_tags"""
48     _get_all_tags = nmlib.notmuch_database_get_all_tags
49     _get_all_tags.restype = c_void_p
50
51     """notmuch_database_create"""
52     _create = nmlib.notmuch_database_create
53     _create.restype = c_void_p
54
55     def __init__(self, path=None, create=False, mode= 0):
56         """If *path* is `None`, we will try to read a users notmuch 
57         configuration and use his configured database. The location of the 
58         configuration file can be specified through the environment variable
59         *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
60
61         If *create* is `True`, the database will always be created in
62         :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
63
64         :param path:   Directory to open/create the database in (see
65                        above for behavior if `None`)
66         :type path:    `str` or `None`
67         :param create: Pass `False` to open an existing, `True` to create a new
68                        database.  
69         :type create:  bool
70         :param mode:   Mode to open a database in. Is always 
71                        :attr:`MODE`.READ_WRITE when creating a new one.
72         :type mode:    :attr:`MODE`
73         :returns:      Nothing
74         :exception:    :exc:`NotmuchError` in case of failure.
75         """
76         self._db = None
77         if path is None:
78             # no path specified. use a user's default database
79             if Database._std_db_path is None:
80                 #the following line throws a NotmuchError if it fails
81                 Database._std_db_path = self._get_user_default_db()
82             path = Database._std_db_path
83
84         if create == False:
85             self.open(path, mode)
86         else:
87             self.create(path)
88
89     def _verify_initialized_db(self):
90         """Raises a NotmuchError in case self._db is still None"""
91         if self._db is None:
92             raise NotmuchError(STATUS.NOT_INITIALIZED)            
93
94     def create(self, path):
95         """Creates a new notmuch database
96
97         This function is used by __init__() and usually does not need
98         to be called directly. It wraps the underlying
99         *notmuch_database_create* function and creates a new notmuch
100         database at *path*. It will always return a database in :attr:`MODE`
101         .READ_WRITE mode as creating an empty database for
102         reading only does not make a great deal of sense.
103
104         :param path: A directory in which we should create the database.
105         :type path: str
106         :returns: Nothing
107         :exception: :exc:`NotmuchError` in case of any failure
108                     (after printing an error message on stderr).
109         """
110         if self._db is not None:
111             raise NotmuchError(
112             message="Cannot create db, this Database() already has an open one.")
113
114         res = Database._create(path, Database.MODE.READ_WRITE)
115
116         if res is None:
117             raise NotmuchError(
118                 message="Could not create the specified database")
119         self._db = res
120
121     def open(self, path, mode= 0): 
122         """Opens an existing database
123
124         This function is used by __init__() and usually does not need
125         to be called directly. It wraps the underlying
126         *notmuch_database_open* function.
127
128         :param status: Open the database in read-only or read-write mode
129         :type status:  :attr:`MODE` 
130         :returns: Nothing
131         :exception: Raises :exc:`NotmuchError` in case
132                     of any failure (after printing an error message on stderr).
133         """
134
135         res = Database._open(path, mode)
136
137         if res is None:
138             raise NotmuchError(
139                 message="Could not open the specified database")
140         self._db = res
141
142     def get_path(self):
143         """Returns the file path of an open database
144
145         Wraps *notmuch_database_get_path*."""
146         # Raise a NotmuchError if not initialized
147         self._verify_initialized_db()
148
149         return Database._get_path(self._db)
150
151     def get_version(self):
152         """Returns the database format version
153
154         :returns: The database version as positive integer
155         :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
156                     the database was not intitialized.
157         """
158         # Raise a NotmuchError if not initialized
159         self._verify_initialized_db()
160
161         return Database._get_version (self._db)
162
163     def needs_upgrade(self):
164         """Does this database need to be upgraded before writing to it?
165
166         If this function returns `True` then no functions that modify the
167         database (:meth:`add_message`,
168         :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
169         etc.) will work unless :meth:`upgrade` is called successfully first.
170
171         :returns: `True` or `False`
172         :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
173                     the database was not intitialized.
174         """
175         # Raise a NotmuchError if not initialized
176         self._verify_initialized_db()
177
178         return notmuch_database_needs_upgrade(self._db) 
179
180     def upgrade(self):
181         """Upgrades the current database
182
183         After opening a database in read-write mode, the client should
184         check if an upgrade is needed (notmuch_database_needs_upgrade) and
185         if so, upgrade with this function before making any modifications.
186
187         NOT IMPLEMENTED: The optional progress_notify callback can be
188         used by the caller to provide progress indication to the
189         user. If non-NULL it will be called periodically with
190         'progress' as a floating-point value in the range of [0.0..1.0] 
191         indicating the progress made so far in the upgrade process.
192
193         :TODO: catch exceptions, document return values and etc...
194         """
195         # Raise a NotmuchError if not initialized
196         self._verify_initialized_db()
197
198         status = Database._upgrade (self._db, None, None)
199         #TODO: catch exceptions, document return values and etc
200         return status
201
202     def get_directory(self, path):
203         """Returns a :class:`Directory` of path, 
204         (creating it if it does not exist(?))
205
206         .. warning:: This call needs a writeable database in 
207            Database.MODE.READ_WRITE mode. The underlying library will exit the
208            program if this method is used on a read-only database!
209
210         :param path: A str containing the path relative to the path of database
211               (see :meth:`get_path`), or else should be an absolute path
212               with initial components that match the path of 'database'.
213         :returns: :class:`Directory` or raises an exception.
214         :exception: :exc:`NotmuchError`
215
216                   STATUS.NOT_INITIALIZED 
217                     If the database was not intitialized.
218
219                   STATUS.FILE_ERROR
220                     If path is not relative database or absolute with initial 
221                     components same as database.
222
223         """
224         # Raise a NotmuchError if not initialized
225         self._verify_initialized_db()
226
227         # sanity checking if path is valid, and make path absolute
228         if path[0] == os.sep:
229             # we got an absolute path
230             if not path.startswith(self.get_path()):
231                 # but its initial components are not equal to the db path
232                 raise NotmuchError(STATUS.FILE_ERROR, 
233                                    message="Database().get_directory() called with a wrong absolute path.")
234             abs_dirpath = path
235         else:
236             #we got a relative path, make it absolute
237             abs_dirpath = os.path.abspath(os.path.join(self.get_path(),path))
238
239         dir_p = Database._get_directory(self._db, path);
240
241         # return the Directory, init it with the absolute path
242         return Directory(abs_dirpath, dir_p, self)
243
244     def add_message(self, filename):
245         """Adds a new message to the database
246
247         `filename` should be a path relative to the path of the open
248         database (see :meth:`get_path`), or else should be an absolute
249         filename with initial components that match the path of the
250         database.
251
252         The file should be a single mail message (not a multi-message mbox)
253         that is expected to remain at its current location, since the
254         notmuch database will reference the filename, and will not copy the
255         entire contents of the file.
256
257         :returns: On success, we return 
258
259            1) a :class:`Message` object that can be used for things
260               such as adding tags to the just-added message.
261            2) one of the following STATUS values:
262
263               STATUS.SUCCESS
264                   Message successfully added to database.
265               STATUS.DUPLICATE_MESSAGE_ID
266                   Message has the same message ID as another message already
267                   in the database. The new filename was successfully added
268                   to the message in the database.
269
270         :rtype:   2-tuple(:class:`Message`, STATUS)
271
272         :exception: Raises a :exc:`NotmuchError` with the following meaning.
273               If such an exception occurs, nothing was added to the database.
274
275               STATUS.FILE_ERROR
276                       An error occurred trying to open the file, (such as 
277                       permission denied, or file not found, etc.).
278               STATUS.FILE_NOT_EMAIL
279                       The contents of filename don't look like an email message.
280               STATUS.READ_ONLY_DATABASE
281                       Database was opened in read-only mode so no message can
282                       be added.
283               STATUS.NOT_INITIALIZED
284                       The database has not been initialized.
285         """
286         # Raise a NotmuchError if not initialized
287         self._verify_initialized_db()
288
289         msg_p = c_void_p()
290         status = nmlib.notmuch_database_add_message(self._db,
291                                                   filename,
292                                                   byref(msg_p))
293  
294         if not status in [STATUS.SUCCESS,STATUS.DUPLICATE_MESSAGE_ID]:
295             raise NotmuchError(status)
296
297         #construct Message() and return
298         msg = Message(msg_p, self)
299         return (msg, status)
300
301     def remove_message(self, filename):
302         """Removes a message from the given notmuch database
303
304         Note that only this particular filename association is removed from
305         the database. If the same message (as determined by the message ID)
306         is still available via other filenames, then the message will
307         persist in the database for those filenames. When the last filename
308         is removed for a particular message, the database content for that
309         message will be entirely removed.
310
311         :returns: A STATUS value with the following meaning:
312
313              STATUS.SUCCESS
314                The last filename was removed and the message was removed 
315                from the database.
316              STATUS.DUPLICATE_MESSAGE_ID
317                This filename was removed but the message persists in the 
318                database with at least one other filename.
319
320         :exception: Raises a :exc:`NotmuchError` with the following meaning.
321              If such an exception occurs, nothing was removed from the database.
322
323              STATUS.READ_ONLY_DATABASE
324                Database was opened in read-only mode so no message can be 
325                removed.
326              STATUS.NOT_INITIALIZED
327                The database has not been initialized.
328         """
329         # Raise a NotmuchError if not initialized
330         self._verify_initialized_db()
331
332         status = nmlib.notmuch_database_remove_message(self._db,
333                                                        filename)
334
335     def find_message(self, msgid):
336         """Returns a :class:`Message` as identified by its message ID
337
338         Wraps the underlying *notmuch_database_find_message* function.
339
340         :param msgid: The message ID
341         :type msgid: string
342         :returns: :class:`Message` or `None` if no message is found or if an
343                   out-of-memory situation occurs.
344         :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
345                   the database was not intitialized.
346         """
347         # Raise a NotmuchError if not initialized
348         self._verify_initialized_db()
349
350         msg_p = Database._find_message(self._db, msgid)
351         if msg_p is None:
352             return None
353         return Message(msg_p, self)
354
355     def get_all_tags(self):
356         """Returns :class:`Tags` with a list of all tags found in the database
357
358         :returns: :class:`Tags`
359         :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
360         """
361         # Raise a NotmuchError if not initialized
362         self._verify_initialized_db()
363
364         tags_p = Database._get_all_tags (self._db)
365         if tags_p == None:
366             raise NotmuchError(STATUS.NULL_POINTER)
367         return Tags(tags_p, self)
368
369     def create_query(self, querystring):
370         """Returns a :class:`Query` derived from this database
371
372         This is a shorthand method for doing::
373
374           # short version
375           # Automatically frees the Database() when 'q' is deleted
376
377           q  = Database(dbpath).create_query('from:"Biene Maja"')
378
379           # long version, which is functionally equivalent but will keep the
380           # Database in the 'db' variable around after we delete 'q':
381
382           db = Database(dbpath)
383           q  = Query(db,'from:"Biene Maja"')
384
385         This function is a python extension and not in the underlying C API.
386         """
387         # Raise a NotmuchError if not initialized
388         self._verify_initialized_db()
389
390         return Query(self, querystring)
391
392     def __repr__(self):
393         return "'Notmuch DB " + self.get_path() + "'"
394
395     def __del__(self):
396         """Close and free the notmuch database if needed"""
397         if self._db is not None:
398             nmlib.notmuch_database_close(self._db)
399
400     def _get_user_default_db(self):
401         """ Reads a user's notmuch config and returns his db location
402
403         Throws a NotmuchError if it cannot find it"""
404         from ConfigParser import SafeConfigParser
405         config = SafeConfigParser()
406         conf_f = os.getenv('NOTMUCH_CONFIG',
407                            os.path.expanduser('~/.notmuch-config'))
408         config.read(conf_f)
409         if not config.has_option('database','path'):
410             raise NotmuchError(message=
411                                "No DB path specified and no user default found")
412         return config.get('database','path')
413
414     @property
415     def db_p(self):
416         """Property returning a pointer to `notmuch_database_t` or `None`
417
418         This should normally not be needed by a user (and is not yet
419         guaranteed to remain stable in future versions).
420         """
421         return self._db
422
423 #------------------------------------------------------------------------------
424 class Query(object):
425     """Represents a search query on an opened :class:`Database`.
426
427     A query selects and filters a subset of messages from the notmuch
428     database we derive from.
429
430     Query() provides an instance attribute :attr:`sort`, which
431     contains the sort order (if specified via :meth:`set_sort`) or
432     `None`.
433
434     Technically, it wraps the underlying *notmuch_query_t* struct.
435
436     .. note:: Do remember that as soon as we tear down this object,
437            all underlying derived objects such as threads,
438            messages, tags etc will be freed by the underlying library
439            as well. Accessing these objects will lead to segfaults and
440            other unexpected behavior. See above for more details.
441     """
442     # constants
443     SORT = Enum(['OLDEST_FIRST','NEWEST_FIRST','MESSAGE_ID'])
444     """Constants: Sort order in which to return results"""
445
446     """notmuch_query_create"""
447     _create = nmlib.notmuch_query_create
448     _create.restype = c_void_p
449
450     """notmuch_query_search_threads"""
451     _search_threads = nmlib.notmuch_query_search_threads
452     _search_threads.restype = c_void_p
453
454     """notmuch_query_search_messages"""
455     _search_messages = nmlib.notmuch_query_search_messages
456     _search_messages.restype = c_void_p
457
458
459     """notmuch_query_count_messages"""
460     _count_messages = nmlib.notmuch_query_count_messages
461     _count_messages.restype = c_uint
462
463     def __init__(self, db, querystr):
464         """
465         :param db: An open database which we derive the Query from.
466         :type db: :class:`Database`
467         :param querystr: The query string for the message.
468         :type querystr: str
469         """
470         self._db = None
471         self._query = None
472         self.sort = None
473         self.create(db, querystr)
474
475     def create(self, db, querystr):
476         """Creates a new query derived from a Database
477
478         This function is utilized by __init__() and usually does not need to 
479         be called directly.
480
481         :param db: Database to create the query from.
482         :type db: :class:`Database`
483         :param querystr: The query string
484         :type querystr: str
485         :returns: Nothing
486         :exception: :exc:`NotmuchError`
487
488                       * STATUS.NOT_INITIALIZED if db is not inited
489                       * STATUS.NULL_POINTER if the query creation failed 
490                         (too little memory)
491         """
492         if db.db_p is None:
493             raise NotmuchError(STATUS.NOT_INITIALIZED)            
494         # create reference to parent db to keep it alive
495         self._db = db
496         
497         # create query, return None if too little mem available
498         query_p = Query._create(db.db_p, querystr)
499         if query_p is None:
500             NotmuchError(STATUS.NULL_POINTER)
501         self._query = query_p
502
503     def set_sort(self, sort):
504         """Set the sort order future results will be delivered in
505
506         Wraps the underlying *notmuch_query_set_sort* function.
507
508         :param sort: Sort order (see :attr:`Query.SORT`)
509         :returns: Nothing
510         :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not 
511                     been initialized.
512         """
513         if self._query is None:
514             raise NotmuchError(STATUS.NOT_INITIALIZED)
515
516         self.sort = sort
517         nmlib.notmuch_query_set_sort(self._query, sort)
518
519     def search_threads(self):
520         """Execute a query for threads
521
522         Execute a query for threads, returning a :class:`Threads` iterator.
523         The returned threads are owned by the query and as such, will only be 
524         valid until the Query is deleted.
525
526         The method sets :attr:`Message.FLAG`\.MATCH for those messages that
527         match the query. The method :meth:`Message.get_flag` allows us
528         to get the value of this flag.
529
530         Technically, it wraps the underlying
531         *notmuch_query_search_threads* function.
532
533         :returns: :class:`Threads`
534         :exception: :exc:`NotmuchError`
535
536                       * STATUS.NOT_INITIALIZED if query is not inited
537                       * STATUS.NULL_POINTER if search_threads failed 
538         """
539         if self._query is None:
540             raise NotmuchError(STATUS.NOT_INITIALIZED)            
541
542         threads_p = Query._search_threads(self._query)
543
544         if threads_p is None:
545             NotmuchError(STATUS.NULL_POINTER)
546
547         return Threads(threads_p,self)
548
549     def search_messages(self):
550         """Filter messages according to the query and return
551         :class:`Messages` in the defined sort order
552
553         Technically, it wraps the underlying
554         *notmuch_query_search_messages* function.
555
556         :returns: :class:`Messages`
557         :exception: :exc:`NotmuchError`
558
559                       * STATUS.NOT_INITIALIZED if query is not inited
560                       * STATUS.NULL_POINTER if search_messages failed 
561         """
562         if self._query is None:
563             raise NotmuchError(STATUS.NOT_INITIALIZED)            
564
565         msgs_p = Query._search_messages(self._query)
566
567         if msgs_p is None:
568             NotmuchError(STATUS.NULL_POINTER)
569
570         return Messages(msgs_p,self)
571
572     def count_messages(self):
573         """Estimate the number of messages matching the query
574
575         This function performs a search and returns Xapian's best
576         guess as to the number of matching messages. It is much faster
577         than performing :meth:`search_messages` and counting the
578         result with `len()` (although it always returned the same
579         result in my tests). Technically, it wraps the underlying
580         *notmuch_query_count_messages* function.
581
582         :returns: :class:`Messages`
583         :exception: :exc:`NotmuchError`
584
585                       * STATUS.NOT_INITIALIZED if query is not inited
586         """
587         if self._query is None:
588             raise NotmuchError(STATUS.NOT_INITIALIZED)            
589
590         return Query._count_messages(self._query)
591
592     def __del__(self):
593         """Close and free the Query"""
594         if self._query is not None:
595             nmlib.notmuch_query_destroy (self._query)
596
597
598 #------------------------------------------------------------------------------
599 class Directory(object):
600     """Represents a directory entry in the notmuch directory
601
602     Modifying attributes of this object will modify the
603     database, not the real directory attributes.
604
605     The Directory object is usually derived from another object
606     e.g. via :meth:`Database.get_directory`, and will automatically be
607     become invalid whenever that parent is deleted. You should
608     therefore initialized this object handing it a reference to the
609     parent, preventing the parent from automatically being garbage
610     collected.
611     """
612
613     """notmuch_directory_get_mtime"""
614     _get_mtime = nmlib.notmuch_directory_get_mtime
615     _get_mtime.restype = c_long
616
617     """notmuch_directory_set_mtime"""
618     _set_mtime = nmlib.notmuch_directory_set_mtime
619     _set_mtime.argtypes = [c_char_p, c_long]
620
621     """notmuch_directory_get_child_files"""
622     _get_child_files = nmlib.notmuch_directory_get_child_files
623     _get_child_files.restype = c_void_p
624
625     """notmuch_directory_get_child_directories"""
626     _get_child_directories = nmlib.notmuch_directory_get_child_directories
627     _get_child_directories.restype = c_void_p
628
629     def _verify_dir_initialized(self):
630         """Raises a NotmuchError(STATUS.NOT_INITIALIZED) if the dir_p is None"""
631         if self._dir_p is None:
632             raise NotmuchError(STATUS.NOT_INITIALIZED)            
633
634     def __init__(self, path, dir_p, parent):
635         """
636         :param path:   The absolute path of the directory object.
637         :param dir_p:  The pointer to an internal notmuch_directory_t object.
638         :param parent: The object this Directory is derived from
639                        (usually a :class:`Database`). We do not directly use
640                        this, but store a reference to it as long as
641                        this Directory object lives. This keeps the
642                        parent object alive.
643         """
644         self._path = path
645         self._dir_p = dir_p
646         self._parent = parent
647
648
649     def set_mtime (self, mtime):
650         """Sets the mtime value of this directory in the database
651
652         The intention is for the caller to use the mtime to allow efficient
653         identification of new messages to be added to the database. The
654         recommended usage is as follows:
655
656         * Read the mtime of a directory from the filesystem
657  
658         * Call :meth:`Database.add_message` for all mail files in
659           the directory
660
661         * Call notmuch_directory_set_mtime with the mtime read from the 
662           filesystem.  Then, when wanting to check for updates to the
663           directory in the future, the client can call :meth:`get_mtime`
664           and know that it only needs to add files if the mtime of the 
665           directory and files are newer than the stored timestamp.
666
667           .. note:: :meth:`get_mtime` function does not allow the caller 
668                  to distinguish a timestamp of 0 from a non-existent
669                  timestamp. So don't store a timestamp of 0 unless you are
670                  comfortable with that.  
671
672           :param mtime: A (time_t) timestamp 
673           :returns: Nothing on success, raising an exception on failure.
674           :exception: :exc:`NotmuchError`:
675
676                         STATUS.XAPIAN_EXCEPTION
677                           A Xapian exception occurred, mtime not stored.
678                         STATUS.READ_ONLY_DATABASE
679                           Database was opened in read-only mode so directory 
680                           mtime cannot be modified.
681                         STATUS.NOT_INITIALIZED
682                           The directory has not been initialized
683         """
684         #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if the dir_p is None
685         self._verify_dir_initialized()
686
687         #TODO: make sure, we convert the mtime parameter to a 'c_long'
688         status = Directory._set_mtime(self._dir_p, mtime)
689
690         #return on success
691         if status == STATUS.SUCCESS:
692             return
693         #fail with Exception otherwise
694         raise NotmuchError(status)
695
696     def get_mtime (self):
697         """Gets the mtime value of this directory in the database
698
699         Retrieves a previously stored mtime for this directory.
700
701         :param mtime: A (time_t) timestamp 
702         :returns: Nothing on success, raising an exception on failure.
703         :exception: :exc:`NotmuchError`:
704
705                         STATUS.NOT_INITIALIZED
706                           The directory has not been initialized
707         """
708         #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if self.dir_p is None
709         self._verify_dir_initialized()
710
711         return Directory._get_mtime (self._dir_p)
712
713     # Make mtime attribute a property of Directory()
714     mtime = property(get_mtime, set_mtime, doc="""Property that allows getting
715                      and setting of the Directory *mtime* (read-write)
716
717                      See :meth:`get_mtime` and :meth:`set_mtime` for usage and 
718                      possible exceptions.""")
719
720     def get_child_files(self):
721         """Gets a Filenames iterator listing all the filenames of
722         messages in the database within the given directory.
723  
724         The returned filenames will be the basename-entries only (not
725         complete paths.
726         """
727         #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if self._dir_p is None
728         self._verify_dir_initialized()
729
730         files_p = Directory._get_child_files(self._dir_p)
731         return Filenames(files_p, self)
732
733     def get_child_directories(self):
734         """Gets a :class:`Filenames` iterator listing all the filenames of
735         sub-directories in the database within the given directory
736         
737         The returned filenames will be the basename-entries only (not
738         complete paths.
739         """
740         #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if self._dir_p is None
741         self._verify_dir_initialized()
742
743         files_p = Directory._get_child_directories(self._dir_p)
744         return Filenames(files_p, self)
745
746     @property
747     def path(self):
748         """Returns the absolute path of this Directory (read-only)"""
749         return self._path
750
751     def __repr__(self):
752         """Object representation"""
753         return "<cnotmuch Directory object '%s'>" % self._path
754
755     def __del__(self):
756         """Close and free the Directory"""
757         if self._dir_p is not None:
758             nmlib.notmuch_directory_destroy(self._dir_p)
759
760 #------------------------------------------------------------------------------
761 class Filenames(object):
762     """An iterator over File- or Directory names that are stored in the database
763     """
764
765     #notmuch_filenames_get
766     _get = nmlib.notmuch_filenames_get
767     _get.restype = c_char_p
768
769     def __init__(self, files_p, parent):
770         """
771         :param files_p: The pointer to an internal notmuch_filenames_t object.
772         :param parent: The object this Directory is derived from
773                        (usually a Directory()). We do not directly use
774                        this, but store a reference to it as long as
775                        this Directory object lives. This keeps the
776                        parent object alive.
777         """
778         self._files_p = files_p
779         self._parent = parent
780
781     def __iter__(self):
782         """ Make Filenames an iterator """
783         return self
784
785     def next(self):
786         if self._files_p is None:
787             raise NotmuchError(STATUS.NOT_INITIALIZED)
788
789         if not nmlib.notmuch_filenames_valid(self._files_p):
790             self._files_p = None
791             raise StopIteration
792
793         file = Filenames._get (self._files_p)
794         nmlib.notmuch_filenames_move_to_next(self._files_p)
795         return file
796
797     def __len__(self):
798         """len(:class:`Filenames`) returns the number of contained files
799
800         .. note:: As this iterates over the files, we will not be able to 
801                iterate over them again! So this will fail::
802
803                  #THIS FAILS
804                  files = Database().get_directory('').get_child_files()
805                  if len(files) > 0:              #this 'exhausts' msgs
806                      # next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!!
807                      for file in files: print file
808         """
809         if self._files_p is None:
810             raise NotmuchError(STATUS.NOT_INITIALIZED)
811
812         i=0
813         while nmlib.notmuch_filenames_valid(self._files_p):
814             nmlib.notmuch_filenames_move_to_next(self._files_p)
815             i += 1
816         self._files_p = None
817         return i
818
819     def __del__(self):
820         """Close and free Filenames"""
821         if self._files_p is not None:
822             nmlib.notmuch_filenames_destroy(self._files_p)