2 This file is part of notmuch.
4 Notmuch is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation, either version 3 of the License, or (at your
7 option) any later version.
9 Notmuch is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 You should have received a copy of the GNU General Public License
15 along with notmuch. If not, see <http://www.gnu.org/licenses/>.
17 Copyright 2010 Sebastian Spaeth <Sebastian@SSpaeth.de>'
21 from ctypes import c_int, c_char_p, c_void_p, c_uint, c_long, byref
22 from notmuch.globals import nmlib, STATUS, NotmuchError, Enum, _str
23 from notmuch.thread import Threads
24 from notmuch.message import Messages, Message
25 from notmuch.tag import Tags
27 class Database(object):
28 """Represents a notmuch database (wraps notmuch_database_t)
30 .. note:: Do remember that as soon as we tear down this object,
31 all underlying derived objects such as queries, threads,
32 messages, tags etc will be freed by the underlying library
33 as well. Accessing these objects will lead to segfaults and
34 other unexpected behavior. See above for more details.
37 """Class attribute to cache user's default database"""
39 MODE = Enum(['READ_ONLY', 'READ_WRITE'])
40 """Constants: Mode in which to open the database"""
42 """notmuch_database_get_directory"""
43 _get_directory = nmlib.notmuch_database_get_directory
44 _get_directory.restype = c_void_p
46 """notmuch_database_get_path"""
47 _get_path = nmlib.notmuch_database_get_path
48 _get_path.restype = c_char_p
50 """notmuch_database_get_version"""
51 _get_version = nmlib.notmuch_database_get_version
52 _get_version.restype = c_uint
54 """notmuch_database_open"""
55 _open = nmlib.notmuch_database_open
56 _open.restype = c_void_p
58 """notmuch_database_upgrade"""
59 _upgrade = nmlib.notmuch_database_upgrade
60 _upgrade.argtypes = [c_void_p, c_void_p, c_void_p]
62 """ notmuch_database_find_message"""
63 _find_message = nmlib.notmuch_database_find_message
64 _find_message.restype = c_void_p
66 """notmuch_database_find_message_by_filename"""
67 _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
68 _find_message_by_filename.restype = c_void_p
70 """notmuch_database_get_all_tags"""
71 _get_all_tags = nmlib.notmuch_database_get_all_tags
72 _get_all_tags.restype = c_void_p
74 """notmuch_database_create"""
75 _create = nmlib.notmuch_database_create
76 _create.restype = c_void_p
78 def __init__(self, path=None, create=False, mode=0):
79 """If *path* is `None`, we will try to read a users notmuch
80 configuration and use his configured database. The location of the
81 configuration file can be specified through the environment variable
82 *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
84 If *create* is `True`, the database will always be created in
85 :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
87 :param path: Directory to open/create the database in (see
88 above for behavior if `None`)
89 :type path: `str` or `None`
90 :param create: Pass `False` to open an existing, `True` to create a new
93 :param mode: Mode to open a database in. Is always
94 :attr:`MODE`.READ_WRITE when creating a new one.
95 :type mode: :attr:`MODE`
97 :exception: :exc:`NotmuchError` in case of failure.
101 # no path specified. use a user's default database
102 if Database._std_db_path is None:
103 #the following line throws a NotmuchError if it fails
104 Database._std_db_path = self._get_user_default_db()
105 path = Database._std_db_path
108 self.open(path, mode)
112 def _verify_initialized_db(self):
113 """Raises a NotmuchError in case self._db is still None"""
115 raise NotmuchError(STATUS.NOT_INITIALIZED)
117 def create(self, path):
118 """Creates a new notmuch database
120 This function is used by __init__() and usually does not need
121 to be called directly. It wraps the underlying
122 *notmuch_database_create* function and creates a new notmuch
123 database at *path*. It will always return a database in :attr:`MODE`
124 .READ_WRITE mode as creating an empty database for
125 reading only does not make a great deal of sense.
127 :param path: A directory in which we should create the database.
130 :exception: :exc:`NotmuchError` in case of any failure
131 (after printing an error message on stderr).
133 if self._db is not None:
134 raise NotmuchError(message="Cannot create db, this Database() "
135 "already has an open one.")
137 res = Database._create(_str(path), Database.MODE.READ_WRITE)
141 message="Could not create the specified database")
144 def open(self, path, mode=0):
145 """Opens an existing database
147 This function is used by __init__() and usually does not need
148 to be called directly. It wraps the underlying
149 *notmuch_database_open* function.
151 :param status: Open the database in read-only or read-write mode
152 :type status: :attr:`MODE`
154 :exception: Raises :exc:`NotmuchError` in case
155 of any failure (after printing an error message on stderr).
157 res = Database._open(_str(path), mode)
161 message="Could not open the specified database")
165 """Returns the file path of an open database
167 Wraps *notmuch_database_get_path*."""
168 # Raise a NotmuchError if not initialized
169 self._verify_initialized_db()
171 return Database._get_path(self._db).decode('utf-8')
173 def get_version(self):
174 """Returns the database format version
176 :returns: The database version as positive integer
177 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
178 the database was not intitialized.
180 # Raise a NotmuchError if not initialized
181 self._verify_initialized_db()
183 return Database._get_version(self._db)
185 def needs_upgrade(self):
186 """Does this database need to be upgraded before writing to it?
188 If this function returns `True` then no functions that modify the
189 database (:meth:`add_message`,
190 :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
191 etc.) will work unless :meth:`upgrade` is called successfully first.
193 :returns: `True` or `False`
194 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
195 the database was not intitialized.
197 # Raise a NotmuchError if not initialized
198 self._verify_initialized_db()
200 return nmlib.notmuch_database_needs_upgrade(self._db)
203 """Upgrades the current database
205 After opening a database in read-write mode, the client should
206 check if an upgrade is needed (notmuch_database_needs_upgrade) and
207 if so, upgrade with this function before making any modifications.
209 NOT IMPLEMENTED: The optional progress_notify callback can be
210 used by the caller to provide progress indication to the
211 user. If non-NULL it will be called periodically with
212 'progress' as a floating-point value in the range of [0.0..1.0]
213 indicating the progress made so far in the upgrade process.
215 :TODO: catch exceptions, document return values and etc...
217 # Raise a NotmuchError if not initialized
218 self._verify_initialized_db()
220 status = Database._upgrade(self._db, None, None)
221 #TODO: catch exceptions, document return values and etc
224 def begin_atomic(self):
225 """Begin an atomic database operation
227 Any modifications performed between a successful
228 :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to
229 the database atomically. Note that, unlike a typical database
230 transaction, this only ensures atomicity, not durability;
231 neither begin nor end necessarily flush modifications to disk.
233 :returns: STATUS.SUCCESS or raises
235 :exception: :exc:`NotmuchError` STATUS.XAPIAN_EXCEPTION::
237 A Xapian exception occurred; atomic section not
239 # Raise a NotmuchError if not initialized
240 self._verify_initialized_db()
241 status = nmlib.notmuch_database_begin_atomic(self._db)
242 if status != STATUS.SUCCESS:
243 raise NotmuchError(status)
246 def end_atomic(self):
247 """Indicate the end of an atomic database operation
249 See :meth:`begin_atomic` for details.
251 :returns: STATUS.SUCCESS or raises
255 STATUS.XAPIAN_EXCEPTION
256 A Xapian exception occurred; atomic section not
258 STATUS.UNBALANCED_ATOMIC:
259 end_atomic has been called more times than begin_atomic."""
260 # Raise a NotmuchError if not initialized
261 self._verify_initialized_db()
262 status = nmlib.notmuch_database_end_atomic(self._db)
263 if status != STATUS.SUCCESS:
264 raise NotmuchError(status)
267 def get_directory(self, path):
268 """Returns a :class:`Directory` of path,
269 (creating it if it does not exist(?))
271 .. warning:: This call needs a writeable database in
272 Database.MODE.READ_WRITE mode. The underlying library will exit the
273 program if this method is used on a read-only database!
275 :param path: An unicode string containing the path relative to the path
276 of database (see :meth:`get_path`), or else should be an absolute path
277 with initial components that match the path of 'database'.
278 :returns: :class:`Directory` or raises an exception.
279 :exception: :exc:`NotmuchError`
281 STATUS.NOT_INITIALIZED
282 If the database was not intitialized.
285 If path is not relative database or absolute with initial
286 components same as database.
289 # Raise a NotmuchError if not initialized
290 self._verify_initialized_db()
292 # sanity checking if path is valid, and make path absolute
293 if path[0] == os.sep:
294 # we got an absolute path
295 if not path.startswith(self.get_path()):
296 # but its initial components are not equal to the db path
297 raise NotmuchError(STATUS.FILE_ERROR,
298 message="Database().get_directory() called "
299 "with a wrong absolute path.")
302 #we got a relative path, make it absolute
303 abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
305 dir_p = Database._get_directory(self._db, _str(path))
307 # return the Directory, init it with the absolute path
308 return Directory(_str(abs_dirpath), dir_p, self)
310 def add_message(self, filename, sync_maildir_flags=False):
311 """Adds a new message to the database
313 :param filename: should be a path relative to the path of the open
314 database (see :meth:`get_path`), or else should be an absolute
315 filename with initial components that match the path of the
318 The file should be a single mail message (not a multi-message mbox)
319 that is expected to remain at its current location, since the
320 notmuch database will reference the filename, and will not copy the
321 entire contents of the file.
323 :param sync_maildir_flags: If the message contains Maildir
324 flags, we will -depending on the notmuch configuration- sync
325 those tags to initial notmuch tags, if set to `True`. It is
326 `False` by default to remain consistent with the libnotmuch
327 API. You might want to look into the underlying method
328 :meth:`Message.maildir_flags_to_tags`.
330 :returns: On success, we return
332 1) a :class:`Message` object that can be used for things
333 such as adding tags to the just-added message.
334 2) one of the following STATUS values:
337 Message successfully added to database.
338 STATUS.DUPLICATE_MESSAGE_ID
339 Message has the same message ID as another message already
340 in the database. The new filename was successfully added
341 to the list of the filenames for the existing message.
343 :rtype: 2-tuple(:class:`Message`, STATUS)
345 :exception: Raises a :exc:`NotmuchError` with the following meaning.
346 If such an exception occurs, nothing was added to the database.
349 An error occurred trying to open the file, (such as
350 permission denied, or file not found, etc.).
351 STATUS.FILE_NOT_EMAIL
352 The contents of filename don't look like an email
354 STATUS.READ_ONLY_DATABASE
355 Database was opened in read-only mode so no message can
357 STATUS.NOT_INITIALIZED
358 The database has not been initialized.
360 # Raise a NotmuchError if not initialized
361 self._verify_initialized_db()
364 status = nmlib.notmuch_database_add_message(self._db,
368 if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
369 raise NotmuchError(status)
371 #construct Message() and return
372 msg = Message(msg_p, self)
373 #automatic sync initial tags from Maildir flags
374 if sync_maildir_flags:
375 msg.maildir_flags_to_tags()
378 def remove_message(self, filename):
379 """Removes a message (filename) from the given notmuch database
381 Note that only this particular filename association is removed from
382 the database. If the same message (as determined by the message ID)
383 is still available via other filenames, then the message will
384 persist in the database for those filenames. When the last filename
385 is removed for a particular message, the database content for that
386 message will be entirely removed.
388 :returns: A STATUS value with the following meaning:
391 The last filename was removed and the message was removed
393 STATUS.DUPLICATE_MESSAGE_ID
394 This filename was removed but the message persists in the
395 database with at least one other filename.
397 :exception: Raises a :exc:`NotmuchError` with the following meaning.
398 If such an exception occurs, nothing was removed from the
401 STATUS.READ_ONLY_DATABASE
402 Database was opened in read-only mode so no message can be
404 STATUS.NOT_INITIALIZED
405 The database has not been initialized.
407 # Raise a NotmuchError if not initialized
408 self._verify_initialized_db()
410 return nmlib.notmuch_database_remove_message(self._db,
413 def find_message(self, msgid):
414 """Returns a :class:`Message` as identified by its message ID
416 Wraps the underlying *notmuch_database_find_message* function.
418 :param msgid: The message ID
420 :returns: :class:`Message` or `None` if no message is found or
421 if any xapian exception or out-of-memory situation
422 occurs. Do note that Xapian Exceptions include
423 "Database modified" situations, e.g. when the
424 notmuch database has been modified by
425 another program in the meantime. A return value of
426 `None` is therefore no guarantee that the message
428 :exception: :exc:`NotmuchError` with STATUS.NOT_INITIALIZED if
429 the database was not intitialized.
431 # Raise a NotmuchError if not initialized
432 self._verify_initialized_db()
434 msg_p = Database._find_message(self._db, _str(msgid))
435 return msg_p and Message(msg_p, self) or None
437 def find_message_by_filename(self, filename):
438 """Find a message with the given filename
440 :returns: If the database contains a message with the given
441 filename, then a class:`Message:` is returned. This
442 function returns None in the following situations:
444 * No message is found with the given filename
445 * An out-of-memory situation occurs
446 * A Xapian exception occurs"""
447 self._verify_initialized_db()
448 msg_p = Database._find_message_by_filename(self._db, _str(filename))
449 return msg_p and Message(msg_p, self) or None
451 def get_all_tags(self):
452 """Returns :class:`Tags` with a list of all tags found in the database
454 :returns: :class:`Tags`
455 :execption: :exc:`NotmuchError` with STATUS.NULL_POINTER on error
457 # Raise a NotmuchError if not initialized
458 self._verify_initialized_db()
460 tags_p = Database._get_all_tags(self._db)
462 raise NotmuchError(STATUS.NULL_POINTER)
463 return Tags(tags_p, self)
465 def create_query(self, querystring):
466 """Returns a :class:`Query` derived from this database
468 This is a shorthand method for doing::
471 # Automatically frees the Database() when 'q' is deleted
473 q = Database(dbpath).create_query('from:"Biene Maja"')
475 # long version, which is functionally equivalent but will keep the
476 # Database in the 'db' variable around after we delete 'q':
478 db = Database(dbpath)
479 q = Query(db,'from:"Biene Maja"')
481 This function is a python extension and not in the underlying C API.
483 # Raise a NotmuchError if not initialized
484 self._verify_initialized_db()
486 return Query(self, querystring)
489 return "'Notmuch DB " + self.get_path() + "'"
492 """Close and free the notmuch database if needed"""
493 if self._db is not None:
494 nmlib.notmuch_database_close(self._db)
496 def _get_user_default_db(self):
497 """ Reads a user's notmuch config and returns his db location
499 Throws a NotmuchError if it cannot find it"""
500 from ConfigParser import SafeConfigParser
501 config = SafeConfigParser()
502 conf_f = os.getenv('NOTMUCH_CONFIG',
503 os.path.expanduser('~/.notmuch-config'))
505 if not config.has_option('database', 'path'):
506 raise NotmuchError(message="No DB path specified"
507 " and no user default found")
508 return config.get('database', 'path').decode('utf-8')
512 """Property returning a pointer to `notmuch_database_t` or `None`
514 This should normally not be needed by a user (and is not yet
515 guaranteed to remain stable in future versions).
521 """Represents a search query on an opened :class:`Database`.
523 A query selects and filters a subset of messages from the notmuch
524 database we derive from.
526 Query() provides an instance attribute :attr:`sort`, which
527 contains the sort order (if specified via :meth:`set_sort`) or
530 Technically, it wraps the underlying *notmuch_query_t* struct.
532 .. note:: Do remember that as soon as we tear down this object,
533 all underlying derived objects such as threads,
534 messages, tags etc will be freed by the underlying library
535 as well. Accessing these objects will lead to segfaults and
536 other unexpected behavior. See above for more details.
539 SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED'])
540 """Constants: Sort order in which to return results"""
542 """notmuch_query_create"""
543 _create = nmlib.notmuch_query_create
544 _create.restype = c_void_p
546 """notmuch_query_search_threads"""
547 _search_threads = nmlib.notmuch_query_search_threads
548 _search_threads.restype = c_void_p
550 """notmuch_query_search_messages"""
551 _search_messages = nmlib.notmuch_query_search_messages
552 _search_messages.restype = c_void_p
554 """notmuch_query_count_messages"""
555 _count_messages = nmlib.notmuch_query_count_messages
556 _count_messages.restype = c_uint
558 def __init__(self, db, querystr):
560 :param db: An open database which we derive the Query from.
561 :type db: :class:`Database`
562 :param querystr: The query string for the message.
563 :type querystr: utf-8 encoded str or unicode
568 self.create(db, querystr)
570 def create(self, db, querystr):
571 """Creates a new query derived from a Database
573 This function is utilized by __init__() and usually does not need to
576 :param db: Database to create the query from.
577 :type db: :class:`Database`
578 :param querystr: The query string
579 :type querystr: utf-8 encoded str or unicode
581 :exception: :exc:`NotmuchError`
583 * STATUS.NOT_INITIALIZED if db is not inited
584 * STATUS.NULL_POINTER if the query creation failed
588 raise NotmuchError(STATUS.NOT_INITIALIZED)
589 # create reference to parent db to keep it alive
591 # create query, return None if too little mem available
592 query_p = Query._create(db.db_p, _str(querystr))
594 NotmuchError(STATUS.NULL_POINTER)
595 self._query = query_p
597 def set_sort(self, sort):
598 """Set the sort order future results will be delivered in
600 Wraps the underlying *notmuch_query_set_sort* function.
602 :param sort: Sort order (see :attr:`Query.SORT`)
604 :exception: :exc:`NotmuchError` STATUS.NOT_INITIALIZED if query has not
607 if self._query is None:
608 raise NotmuchError(STATUS.NOT_INITIALIZED)
611 nmlib.notmuch_query_set_sort(self._query, sort)
613 def search_threads(self):
614 """Execute a query for threads
616 Execute a query for threads, returning a :class:`Threads` iterator.
617 The returned threads are owned by the query and as such, will only be
618 valid until the Query is deleted.
620 The method sets :attr:`Message.FLAG`\.MATCH for those messages that
621 match the query. The method :meth:`Message.get_flag` allows us
622 to get the value of this flag.
624 Technically, it wraps the underlying
625 *notmuch_query_search_threads* function.
627 :returns: :class:`Threads`
628 :exception: :exc:`NotmuchError`
630 * STATUS.NOT_INITIALIZED if query is not inited
631 * STATUS.NULL_POINTER if search_threads failed
633 if self._query is None:
634 raise NotmuchError(STATUS.NOT_INITIALIZED)
636 threads_p = Query._search_threads(self._query)
638 if threads_p is None:
639 raise NotmuchError(STATUS.NULL_POINTER)
641 return Threads(threads_p, self)
643 def search_messages(self):
644 """Filter messages according to the query and return
645 :class:`Messages` in the defined sort order
647 Technically, it wraps the underlying
648 *notmuch_query_search_messages* function.
650 :returns: :class:`Messages`
651 :exception: :exc:`NotmuchError`
653 * STATUS.NOT_INITIALIZED if query is not inited
654 * STATUS.NULL_POINTER if search_messages failed
656 if self._query is None:
657 raise NotmuchError(STATUS.NOT_INITIALIZED)
659 msgs_p = Query._search_messages(self._query)
662 NotmuchError(STATUS.NULL_POINTER)
664 return Messages(msgs_p, self)
666 def count_messages(self):
667 """Estimate the number of messages matching the query
669 This function performs a search and returns Xapian's best
670 guess as to the number of matching messages. It is much faster
671 than performing :meth:`search_messages` and counting the
672 result with `len()` (although it always returned the same
673 result in my tests). Technically, it wraps the underlying
674 *notmuch_query_count_messages* function.
676 :returns: :class:`Messages`
677 :exception: :exc:`NotmuchError`
679 * STATUS.NOT_INITIALIZED if query is not inited
681 if self._query is None:
682 raise NotmuchError(STATUS.NOT_INITIALIZED)
684 return Query._count_messages(self._query)
687 """Close and free the Query"""
688 if self._query is not None:
689 nmlib.notmuch_query_destroy(self._query)
692 class Directory(object):
693 """Represents a directory entry in the notmuch directory
695 Modifying attributes of this object will modify the
696 database, not the real directory attributes.
698 The Directory object is usually derived from another object
699 e.g. via :meth:`Database.get_directory`, and will automatically be
700 become invalid whenever that parent is deleted. You should
701 therefore initialized this object handing it a reference to the
702 parent, preventing the parent from automatically being garbage
706 """notmuch_directory_get_mtime"""
707 _get_mtime = nmlib.notmuch_directory_get_mtime
708 _get_mtime.restype = c_long
710 """notmuch_directory_set_mtime"""
711 _set_mtime = nmlib.notmuch_directory_set_mtime
712 _set_mtime.argtypes = [c_char_p, c_long]
714 """notmuch_directory_get_child_files"""
715 _get_child_files = nmlib.notmuch_directory_get_child_files
716 _get_child_files.restype = c_void_p
718 """notmuch_directory_get_child_directories"""
719 _get_child_directories = nmlib.notmuch_directory_get_child_directories
720 _get_child_directories.restype = c_void_p
722 def _verify_dir_initialized(self):
723 """Raises a NotmuchError(STATUS.NOT_INITIALIZED) if dir_p is None"""
724 if self._dir_p is None:
725 raise NotmuchError(STATUS.NOT_INITIALIZED)
727 def __init__(self, path, dir_p, parent):
729 :param path: The absolute path of the directory object as unicode.
730 :param dir_p: The pointer to an internal notmuch_directory_t object.
731 :param parent: The object this Directory is derived from
732 (usually a :class:`Database`). We do not directly use
733 this, but store a reference to it as long as
734 this Directory object lives. This keeps the
737 assert isinstance(path, unicode), "Path needs to be an UNICODE object"
740 self._parent = parent
742 def set_mtime(self, mtime):
743 """Sets the mtime value of this directory in the database
745 The intention is for the caller to use the mtime to allow efficient
746 identification of new messages to be added to the database. The
747 recommended usage is as follows:
749 * Read the mtime of a directory from the filesystem
751 * Call :meth:`Database.add_message` for all mail files in
754 * Call notmuch_directory_set_mtime with the mtime read from the
755 filesystem. Then, when wanting to check for updates to the
756 directory in the future, the client can call :meth:`get_mtime`
757 and know that it only needs to add files if the mtime of the
758 directory and files are newer than the stored timestamp.
760 .. note:: :meth:`get_mtime` function does not allow the caller
761 to distinguish a timestamp of 0 from a non-existent
762 timestamp. So don't store a timestamp of 0 unless you are
763 comfortable with that.
765 :param mtime: A (time_t) timestamp
766 :returns: Nothing on success, raising an exception on failure.
767 :exception: :exc:`NotmuchError`:
769 STATUS.XAPIAN_EXCEPTION
770 A Xapian exception occurred, mtime not stored.
771 STATUS.READ_ONLY_DATABASE
772 Database was opened in read-only mode so directory
773 mtime cannot be modified.
774 STATUS.NOT_INITIALIZED
775 The directory has not been initialized
777 #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if the dir_p is None
778 self._verify_dir_initialized()
780 #TODO: make sure, we convert the mtime parameter to a 'c_long'
781 status = Directory._set_mtime(self._dir_p, mtime)
784 if status == STATUS.SUCCESS:
786 #fail with Exception otherwise
787 raise NotmuchError(status)
790 """Gets the mtime value of this directory in the database
792 Retrieves a previously stored mtime for this directory.
794 :param mtime: A (time_t) timestamp
795 :returns: Nothing on success, raising an exception on failure.
796 :exception: :exc:`NotmuchError`:
798 STATUS.NOT_INITIALIZED
799 The directory has not been initialized
801 #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if self.dir_p is None
802 self._verify_dir_initialized()
804 return Directory._get_mtime(self._dir_p)
806 # Make mtime attribute a property of Directory()
807 mtime = property(get_mtime, set_mtime, doc="""Property that allows getting
808 and setting of the Directory *mtime* (read-write)
810 See :meth:`get_mtime` and :meth:`set_mtime` for usage and
811 possible exceptions.""")
813 def get_child_files(self):
814 """Gets a Filenames iterator listing all the filenames of
815 messages in the database within the given directory.
817 The returned filenames will be the basename-entries only (not
820 #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if self._dir_p is None
821 self._verify_dir_initialized()
823 files_p = Directory._get_child_files(self._dir_p)
824 return Filenames(files_p, self)
826 def get_child_directories(self):
827 """Gets a :class:`Filenames` iterator listing all the filenames of
828 sub-directories in the database within the given directory
830 The returned filenames will be the basename-entries only (not
833 #Raise a NotmuchError(STATUS.NOT_INITIALIZED) if self._dir_p is None
834 self._verify_dir_initialized()
836 files_p = Directory._get_child_directories(self._dir_p)
837 return Filenames(files_p, self)
841 """Returns the absolute path of this Directory (read-only)"""
845 """Object representation"""
846 return "<notmuch Directory object '%s'>" % self._path
849 """Close and free the Directory"""
850 if self._dir_p is not None:
851 nmlib.notmuch_directory_destroy(self._dir_p)
854 class Filenames(object):
855 """An iterator over File- or Directory names stored in the database"""
857 #notmuch_filenames_get
858 _get = nmlib.notmuch_filenames_get
859 _get.restype = c_char_p
861 def __init__(self, files_p, parent):
863 :param files_p: The pointer to an internal notmuch_filenames_t object.
864 :param parent: The object this Directory is derived from
865 (usually a Directory()). We do not directly use
866 this, but store a reference to it as long as
867 this Directory object lives. This keeps the
870 self._files_p = files_p
871 self._parent = parent
874 """ Make Filenames an iterator """
878 if self._files_p is None:
879 raise NotmuchError(STATUS.NOT_INITIALIZED)
881 if not nmlib.notmuch_filenames_valid(self._files_p):
885 file = Filenames._get(self._files_p)
886 nmlib.notmuch_filenames_move_to_next(self._files_p)
890 """len(:class:`Filenames`) returns the number of contained files
892 .. note:: As this iterates over the files, we will not be able to
893 iterate over them again! So this will fail::
896 files = Database().get_directory('').get_child_files()
897 if len(files) > 0: #this 'exhausts' msgs
898 # next line raises NotmuchError(STATUS.NOT_INITIALIZED)!!!
899 for file in files: print file
901 if self._files_p is None:
902 raise NotmuchError(STATUS.NOT_INITIALIZED)
905 while nmlib.notmuch_filenames_valid(self._files_p):
906 nmlib.notmuch_filenames_move_to_next(self._files_p)
912 """Close and free Filenames"""
913 if self._files_p is not None:
914 nmlib.notmuch_filenames_destroy(self._files_p)