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>'
22 from ctypes import c_char_p, c_void_p, c_uint, c_long, byref, POINTER
23 from notmuch.globals import (nmlib, STATUS, NotmuchError, NotInitializedError,
24 NullPointerError, Enum, _str,
25 NotmuchDatabaseP, NotmuchDirectoryP, NotmuchMessageP, NotmuchTagsP,
26 NotmuchQueryP, NotmuchMessagesP, NotmuchThreadsP, NotmuchFilenamesP)
27 from notmuch.thread import Threads
28 from notmuch.message import Messages, Message
29 from notmuch.tag import Tags
32 class Database(object):
33 """The :class:`Database` is the highest-level object that notmuch
34 provides. It references a notmuch database, and can be opened in
35 read-only or read-write mode. A :class:`Query` can be derived from
36 or be applied to a specific database to find messages. Also adding
37 and removing messages to the database happens via this
38 object. Modifications to the database are not atmic by default (see
39 :meth:`begin_atomic`) and once a database has been modified, all
40 other database objects pointing to the same data-base will throw an
41 :exc:`XapianError` as the underlying database has been
42 modified. Close and reopen the database to continue working with it.
44 :class:`Database` objects implement the context manager protocol
45 so you can use the :keyword:`with` statement to ensure that the
46 database is properly closed.
50 Any function in this class can and will throw an
51 :exc:`NotInitializedError` if the database was not intitialized
56 Do remember that as soon as we tear down (e.g. via `del db`) this
57 object, all underlying derived objects such as queries, threads,
58 messages, tags etc will be freed by the underlying library as well.
59 Accessing these objects will lead to segfaults and other unexpected
60 behavior. See above for more details.
63 """Class attribute to cache user's default database"""
65 MODE = Enum(['READ_ONLY', 'READ_WRITE'])
66 """Constants: Mode in which to open the database"""
68 """notmuch_database_get_directory"""
69 _get_directory = nmlib.notmuch_database_get_directory
70 _get_directory.argtypes = [NotmuchDatabaseP, c_char_p]
71 _get_directory.restype = NotmuchDirectoryP
73 """notmuch_database_get_path"""
74 _get_path = nmlib.notmuch_database_get_path
75 _get_path.argtypes = [NotmuchDatabaseP]
76 _get_path.restype = c_char_p
78 """notmuch_database_get_version"""
79 _get_version = nmlib.notmuch_database_get_version
80 _get_version.argtypes = [NotmuchDatabaseP]
81 _get_version.restype = c_uint
83 """notmuch_database_open"""
84 _open = nmlib.notmuch_database_open
85 _open.argtypes = [c_char_p, c_uint]
86 _open.restype = NotmuchDatabaseP
88 """notmuch_database_upgrade"""
89 _upgrade = nmlib.notmuch_database_upgrade
90 _upgrade.argtypes = [NotmuchDatabaseP, c_void_p, c_void_p]
91 _upgrade.restype = c_uint
93 """ notmuch_database_find_message"""
94 _find_message = nmlib.notmuch_database_find_message
95 _find_message.argtypes = [NotmuchDatabaseP, c_char_p,
96 POINTER(NotmuchMessageP)]
97 _find_message.restype = c_uint
99 """notmuch_database_find_message_by_filename"""
100 _find_message_by_filename = nmlib.notmuch_database_find_message_by_filename
101 _find_message_by_filename.argtypes = [NotmuchDatabaseP, c_char_p,
102 POINTER(NotmuchMessageP)]
103 _find_message_by_filename.restype = c_uint
105 """notmuch_database_get_all_tags"""
106 _get_all_tags = nmlib.notmuch_database_get_all_tags
107 _get_all_tags.argtypes = [NotmuchDatabaseP]
108 _get_all_tags.restype = NotmuchTagsP
110 """notmuch_database_create"""
111 _create = nmlib.notmuch_database_create
112 _create.argtypes = [c_char_p]
113 _create.restype = NotmuchDatabaseP
115 def __init__(self, path=None, create=False, mode=0):
116 """If *path* is `None`, we will try to read a users notmuch
117 configuration and use his configured database. The location of the
118 configuration file can be specified through the environment variable
119 *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
121 If *create* is `True`, the database will always be created in
122 :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
124 :param path: Directory to open/create the database in (see
125 above for behavior if `None`)
126 :type path: `str` or `None`
127 :param create: Pass `False` to open an existing, `True` to create a new
130 :param mode: Mode to open a database in. Is always
131 :attr:`MODE`.READ_WRITE when creating a new one.
132 :type mode: :attr:`MODE`
133 :exception: :exc:`NotmuchError` or derived exception in case of
138 # no path specified. use a user's default database
139 if Database._std_db_path is None:
140 #the following line throws a NotmuchError if it fails
141 Database._std_db_path = self._get_user_default_db()
142 path = Database._std_db_path
145 self.open(path, mode)
152 def _assert_db_is_initialized(self):
153 """Raises :exc:`NotInitializedError` if self._db is `None`"""
155 raise NotInitializedError()
157 def create(self, path):
158 """Creates a new notmuch database
160 This function is used by __init__() and usually does not need
161 to be called directly. It wraps the underlying
162 *notmuch_database_create* function and creates a new notmuch
163 database at *path*. It will always return a database in :attr:`MODE`
164 .READ_WRITE mode as creating an empty database for
165 reading only does not make a great deal of sense.
167 :param path: A directory in which we should create the database.
170 :exception: :exc:`NotmuchError` in case of any failure
171 (possibly after printing an error message on stderr).
173 if self._db is not None:
174 raise NotmuchError(message="Cannot create db, this Database() "
175 "already has an open one.")
177 res = Database._create(_str(path), Database.MODE.READ_WRITE)
181 message="Could not create the specified database")
184 def open(self, path, mode=0):
185 """Opens an existing database
187 This function is used by __init__() and usually does not need
188 to be called directly. It wraps the underlying
189 *notmuch_database_open* function.
191 :param status: Open the database in read-only or read-write mode
192 :type status: :attr:`MODE`
194 :exception: Raises :exc:`NotmuchError` in case of any failure
195 (possibly after printing an error message on stderr).
197 res = Database._open(_str(path), mode)
200 raise NotmuchError(message="Could not open the specified database")
203 _close = nmlib.notmuch_database_close
204 _close.argtypes = [NotmuchDatabaseP]
205 _close.restype = None
208 """Close and free the notmuch database if needed"""
209 if self._db is not None:
210 self._close(self._db)
215 Implements the context manager protocol.
219 def __exit__(self, exc_type, exc_value, traceback):
221 Implements the context manager protocol.
226 """Returns the file path of an open database"""
227 self._assert_db_is_initialized()
228 return Database._get_path(self._db).decode('utf-8')
230 def get_version(self):
231 """Returns the database format version
233 :returns: The database version as positive integer
235 self._assert_db_is_initialized()
236 return Database._get_version(self._db)
238 _needs_upgrade = nmlib.notmuch_database_needs_upgrade
239 _needs_upgrade.argtypes = [NotmuchDatabaseP]
240 _needs_upgrade.restype = bool
242 def needs_upgrade(self):
243 """Does this database need to be upgraded before writing to it?
245 If this function returns `True` then no functions that modify the
246 database (:meth:`add_message`,
247 :meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
248 etc.) will work unless :meth:`upgrade` is called successfully first.
250 :returns: `True` or `False`
252 self._assert_db_is_initialized()
253 return self._needs_upgrade(self._db)
256 """Upgrades the current database
258 After opening a database in read-write mode, the client should
259 check if an upgrade is needed (notmuch_database_needs_upgrade) and
260 if so, upgrade with this function before making any modifications.
262 NOT IMPLEMENTED: The optional progress_notify callback can be
263 used by the caller to provide progress indication to the
264 user. If non-NULL it will be called periodically with
265 'progress' as a floating-point value in the range of [0.0..1.0]
266 indicating the progress made so far in the upgrade process.
268 :TODO: catch exceptions, document return values and etc...
270 self._assert_db_is_initialized()
271 status = Database._upgrade(self._db, None, None)
272 #TODO: catch exceptions, document return values and etc
275 _begin_atomic = nmlib.notmuch_database_begin_atomic
276 _begin_atomic.argtypes = [NotmuchDatabaseP]
277 _begin_atomic.restype = c_uint
279 def begin_atomic(self):
280 """Begin an atomic database operation
282 Any modifications performed between a successful
283 :meth:`begin_atomic` and a :meth:`end_atomic` will be applied to
284 the database atomically. Note that, unlike a typical database
285 transaction, this only ensures atomicity, not durability;
286 neither begin nor end necessarily flush modifications to disk.
288 :returns: :attr:`STATUS`.SUCCESS or raises
289 :exception: :exc:`NotmuchError`: :attr:`STATUS`.XAPIAN_EXCEPTION
290 Xapian exception occurred; atomic section not entered.
292 *Added in notmuch 0.9*"""
293 self._assert_db_is_initialized()
294 status = self._begin_atomic(self._db)
295 if status != STATUS.SUCCESS:
296 raise NotmuchError(status)
299 _end_atomic = nmlib.notmuch_database_end_atomic
300 _end_atomic.argtypes = [NotmuchDatabaseP]
301 _end_atomic.restype = c_uint
303 def end_atomic(self):
304 """Indicate the end of an atomic database operation
306 See :meth:`begin_atomic` for details.
308 :returns: :attr:`STATUS`.SUCCESS or raises
312 :attr:`STATUS`.XAPIAN_EXCEPTION
313 A Xapian exception occurred; atomic section not
315 :attr:`STATUS`.UNBALANCED_ATOMIC:
316 end_atomic has been called more times than begin_atomic.
318 *Added in notmuch 0.9*"""
319 self._assert_db_is_initialized()
320 status = self._end_atomic(self._db)
321 if status != STATUS.SUCCESS:
322 raise NotmuchError(status)
325 def get_directory(self, path):
326 """Returns a :class:`Directory` of path,
327 (creating it if it does not exist(?))
331 This call needs a writeable database in
332 :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
333 exit the program if this method is used on a read-only database!
335 :param path: An unicode string containing the path relative to the path
336 of database (see :meth:`get_path`), or else should be an absolute
337 path with initial components that match the path of 'database'.
338 :returns: :class:`Directory` or raises an exception.
340 :exc:`NotmuchError` with :attr:`STATUS`.FILE_ERROR
341 If path is not relative database or absolute with initial
342 components same as database.
344 self._assert_db_is_initialized()
345 # sanity checking if path is valid, and make path absolute
346 if path[0] == os.sep:
347 # we got an absolute path
348 if not path.startswith(self.get_path()):
349 # but its initial components are not equal to the db path
350 raise NotmuchError(STATUS.FILE_ERROR,
351 message="Database().get_directory() called "
352 "with a wrong absolute path.")
355 #we got a relative path, make it absolute
356 abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
358 dir_p = Database._get_directory(self._db, _str(path))
360 # return the Directory, init it with the absolute path
361 return Directory(_str(abs_dirpath), dir_p, self)
363 _add_message = nmlib.notmuch_database_add_message
364 _add_message.argtypes = [NotmuchDatabaseP, c_char_p,
365 POINTER(NotmuchMessageP)]
366 _add_message.restype = c_uint
368 def add_message(self, filename, sync_maildir_flags=False):
369 """Adds a new message to the database
371 :param filename: should be a path relative to the path of the
372 open database (see :meth:`get_path`), or else should be an
373 absolute filename with initial components that match the
374 path of the database.
376 The file should be a single mail message (not a
377 multi-message mbox) that is expected to remain at its
378 current location, since the notmuch database will reference
379 the filename, and will not copy the entire contents of the
382 :param sync_maildir_flags: If the message contains Maildir
383 flags, we will -depending on the notmuch configuration- sync
384 those tags to initial notmuch tags, if set to `True`. It is
385 `False` by default to remain consistent with the libnotmuch
386 API. You might want to look into the underlying method
387 :meth:`Message.maildir_flags_to_tags`.
389 :returns: On success, we return
391 1) a :class:`Message` object that can be used for things
392 such as adding tags to the just-added message.
393 2) one of the following :attr:`STATUS` values:
395 :attr:`STATUS`.SUCCESS
396 Message successfully added to database.
397 :attr:`STATUS`.DUPLICATE_MESSAGE_ID
398 Message has the same message ID as another message already
399 in the database. The new filename was successfully added
400 to the list of the filenames for the existing message.
402 :rtype: 2-tuple(:class:`Message`, :attr:`STATUS`)
404 :exception: Raises a :exc:`NotmuchError` with the following meaning.
405 If such an exception occurs, nothing was added to the database.
407 :attr:`STATUS`.FILE_ERROR
408 An error occurred trying to open the file, (such as
409 permission denied, or file not found, etc.).
410 :attr:`STATUS`.FILE_NOT_EMAIL
411 The contents of filename don't look like an email
413 :attr:`STATUS`.READ_ONLY_DATABASE
414 Database was opened in read-only mode so no message can
417 self._assert_db_is_initialized()
418 msg_p = NotmuchMessageP()
419 status = self._add_message(self._db, _str(filename), byref(msg_p))
421 if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
422 raise NotmuchError(status)
424 #construct Message() and return
425 msg = Message(msg_p, self)
426 #automatic sync initial tags from Maildir flags
427 if sync_maildir_flags:
428 msg.maildir_flags_to_tags()
431 _remove_message = nmlib.notmuch_database_remove_message
432 _remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
433 _remove_message.restype = c_uint
435 def remove_message(self, filename):
436 """Removes a message (filename) from the given notmuch database
438 Note that only this particular filename association is removed from
439 the database. If the same message (as determined by the message ID)
440 is still available via other filenames, then the message will
441 persist in the database for those filenames. When the last filename
442 is removed for a particular message, the database content for that
443 message will be entirely removed.
445 :returns: A :attr:`STATUS` value with the following meaning:
447 :attr:`STATUS`.SUCCESS
448 The last filename was removed and the message was removed
450 :attr:`STATUS`.DUPLICATE_MESSAGE_ID
451 This filename was removed but the message persists in the
452 database with at least one other filename.
454 :exception: Raises a :exc:`NotmuchError` with the following meaning.
455 If such an exception occurs, nothing was removed from the
458 :attr:`STATUS`.READ_ONLY_DATABASE
459 Database was opened in read-only mode so no message can be
462 self._assert_db_is_initialized()
463 return self._remove_message(self._db, _str(filename))
465 def find_message(self, msgid):
466 """Returns a :class:`Message` as identified by its message ID
468 Wraps the underlying *notmuch_database_find_message* function.
470 :param msgid: The message ID
471 :type msgid: unicode or str
472 :returns: :class:`Message` or `None` if no message is found.
474 :exc:`OutOfMemoryError`
475 If an Out-of-memory occured while constructing the message.
477 In case of a Xapian Exception. These exceptions
478 include "Database modified" situations, e.g. when the
479 notmuch database has been modified by another program
480 in the meantime. In this case, you should close and
481 reopen the database and retry.
482 :exc:`NotInitializedError` if
483 the database was not intitialized.
485 self._assert_db_is_initialized()
486 msg_p = NotmuchMessageP()
487 status = Database._find_message(self._db, _str(msgid), byref(msg_p))
488 if status != STATUS.SUCCESS:
489 raise NotmuchError(status)
490 return msg_p and Message(msg_p, self) or None
492 def find_message_by_filename(self, filename):
493 """Find a message with the given filename
497 This call needs a writeable database in
498 :attr:`Database.MODE`.READ_WRITE mode. The underlying library will
499 exit the program if this method is used on a read-only database!
501 :returns: If the database contains a message with the given
502 filename, then a class:`Message:` is returned. This
503 function returns None if no message is found with the given
507 :exc:`OutOfMemoryError`
508 If an Out-of-memory occured while constructing the message.
510 In case of a Xapian Exception. These exceptions
511 include "Database modified" situations, e.g. when the
512 notmuch database has been modified by another program
513 in the meantime. In this case, you should close and
514 reopen the database and retry.
515 :exc:`NotInitializedError` if
516 the database was not intitialized.
518 *Added in notmuch 0.9*"""
519 self._assert_db_is_initialized()
520 msg_p = NotmuchMessageP()
521 status = Database._find_message_by_filename(self._db, _str(filename),
523 if status != STATUS.SUCCESS:
524 raise NotmuchError(status)
525 return msg_p and Message(msg_p, self) or None
527 def get_all_tags(self):
528 """Returns :class:`Tags` with a list of all tags found in the database
530 :returns: :class:`Tags`
531 :execption: :exc:`NotmuchError` with :attr:`STATUS`.NULL_POINTER
534 self._assert_db_is_initialized()
535 tags_p = Database._get_all_tags(self._db)
537 raise NotmuchError(STATUS.NULL_POINTER)
538 return Tags(tags_p, self)
540 def create_query(self, querystring):
541 """Returns a :class:`Query` derived from this database
543 This is a shorthand method for doing::
546 # Automatically frees the Database() when 'q' is deleted
548 q = Database(dbpath).create_query('from:"Biene Maja"')
550 # long version, which is functionally equivalent but will keep the
551 # Database in the 'db' variable around after we delete 'q':
553 db = Database(dbpath)
554 q = Query(db,'from:"Biene Maja"')
556 This function is a python extension and not in the underlying C API.
558 return Query(self, querystring)
561 return "'Notmuch DB " + self.get_path() + "'"
563 def _get_user_default_db(self):
564 """ Reads a user's notmuch config and returns his db location
566 Throws a NotmuchError if it cannot find it"""
569 from configparser import SafeConfigParser
572 from ConfigParser import SafeConfigParser
574 config = SafeConfigParser()
575 conf_f = os.getenv('NOTMUCH_CONFIG',
576 os.path.expanduser('~/.notmuch-config'))
577 config.readfp(codecs.open(conf_f, 'r', 'utf-8'))
578 if not config.has_option('database', 'path'):
579 raise NotmuchError(message="No DB path specified"
580 " and no user default found")
581 return config.get('database', 'path')
585 """Property returning a pointer to `notmuch_database_t` or `None`
587 This should normally not be needed by a user (and is not yet
588 guaranteed to remain stable in future versions).
594 """Represents a search query on an opened :class:`Database`.
596 A query selects and filters a subset of messages from the notmuch
597 database we derive from.
599 :class:`Query` provides an instance attribute :attr:`sort`, which
600 contains the sort order (if specified via :meth:`set_sort`) or
603 Any function in this class may throw an :exc:`NotInitializedError`
604 in case the underlying query object was not set up correctly.
606 .. note:: Do remember that as soon as we tear down this object,
607 all underlying derived objects such as threads,
608 messages, tags etc will be freed by the underlying library
609 as well. Accessing these objects will lead to segfaults and
610 other unexpected behavior. See above for more details.
613 SORT = Enum(['OLDEST_FIRST', 'NEWEST_FIRST', 'MESSAGE_ID', 'UNSORTED'])
614 """Constants: Sort order in which to return results"""
616 """notmuch_query_create"""
617 _create = nmlib.notmuch_query_create
618 _create.argtypes = [NotmuchDatabaseP, c_char_p]
619 _create.restype = NotmuchQueryP
621 """notmuch_query_search_threads"""
622 _search_threads = nmlib.notmuch_query_search_threads
623 _search_threads.argtypes = [NotmuchQueryP]
624 _search_threads.restype = NotmuchThreadsP
626 """notmuch_query_search_messages"""
627 _search_messages = nmlib.notmuch_query_search_messages
628 _search_messages.argtypes = [NotmuchQueryP]
629 _search_messages.restype = NotmuchMessagesP
631 """notmuch_query_count_messages"""
632 _count_messages = nmlib.notmuch_query_count_messages
633 _count_messages.argtypes = [NotmuchQueryP]
634 _count_messages.restype = c_uint
636 def __init__(self, db, querystr):
638 :param db: An open database which we derive the Query from.
639 :type db: :class:`Database`
640 :param querystr: The query string for the message.
641 :type querystr: utf-8 encoded str or unicode
646 self.create(db, querystr)
648 def _assert_query_is_initialized(self):
649 """Raises :exc:`NotInitializedError` if self._query is `None`"""
650 if self._query is None:
651 raise NotInitializedError()
653 def create(self, db, querystr):
654 """Creates a new query derived from a Database
656 This function is utilized by __init__() and usually does not need to
659 :param db: Database to create the query from.
660 :type db: :class:`Database`
661 :param querystr: The query string
662 :type querystr: utf-8 encoded str or unicode
665 :exc:`NullPointerError` if the query creation failed
666 (e.g. too little memory).
667 :exc:`NotInitializedError` if the underlying db was not
670 db._assert_db_is_initialized()
671 # create reference to parent db to keep it alive
673 # create query, return None if too little mem available
674 query_p = Query._create(db.db_p, _str(querystr))
676 raise NullPointerError
677 self._query = query_p
679 _set_sort = nmlib.notmuch_query_set_sort
680 _set_sort.argtypes = [NotmuchQueryP, c_uint]
681 _set_sort.argtypes = None
683 def set_sort(self, sort):
684 """Set the sort order future results will be delivered in
686 :param sort: Sort order (see :attr:`Query.SORT`)
688 self._assert_query_is_initialized()
690 self._set_sort(self._query, sort)
692 def search_threads(self):
693 """Execute a query for threads
695 Execute a query for threads, returning a :class:`Threads` iterator.
696 The returned threads are owned by the query and as such, will only be
697 valid until the Query is deleted.
699 The method sets :attr:`Message.FLAG`\.MATCH for those messages that
700 match the query. The method :meth:`Message.get_flag` allows us
701 to get the value of this flag.
703 :returns: :class:`Threads`
704 :exception: :exc:`NullPointerError` if search_threads failed
706 self._assert_query_is_initialized()
707 threads_p = Query._search_threads(self._query)
710 raise NullPointerError
711 return Threads(threads_p, self)
713 def search_messages(self):
714 """Filter messages according to the query and return
715 :class:`Messages` in the defined sort order
717 :returns: :class:`Messages`
718 :exception: :exc:`NullPointerError` if search_messages failed
720 self._assert_query_is_initialized()
721 msgs_p = Query._search_messages(self._query)
724 raise NullPointerError
725 return Messages(msgs_p, self)
727 def count_messages(self):
728 """Estimate the number of messages matching the query
730 This function performs a search and returns Xapian's best
731 guess as to the number of matching messages. It is much faster
732 than performing :meth:`search_messages` and counting the
733 result with `len()` (although it always returned the same
734 result in my tests). Technically, it wraps the underlying
735 *notmuch_query_count_messages* function.
737 :returns: :class:`Messages`
739 self._assert_query_is_initialized()
740 return Query._count_messages(self._query)
742 _destroy = nmlib.notmuch_query_destroy
743 _destroy.argtypes = [NotmuchQueryP]
744 _destroy.restype = None
747 """Close and free the Query"""
748 if self._query is not None:
749 self._destroy(self._query)
752 class Directory(object):
753 """Represents a directory entry in the notmuch directory
755 Modifying attributes of this object will modify the
756 database, not the real directory attributes.
758 The Directory object is usually derived from another object
759 e.g. via :meth:`Database.get_directory`, and will automatically be
760 become invalid whenever that parent is deleted. You should
761 therefore initialized this object handing it a reference to the
762 parent, preventing the parent from automatically being garbage
766 """notmuch_directory_get_mtime"""
767 _get_mtime = nmlib.notmuch_directory_get_mtime
768 _get_mtime.argtypes = [NotmuchDirectoryP]
769 _get_mtime.restype = c_long
771 """notmuch_directory_set_mtime"""
772 _set_mtime = nmlib.notmuch_directory_set_mtime
773 _set_mtime.argtypes = [NotmuchDirectoryP, c_long]
774 _set_mtime.restype = c_uint
776 """notmuch_directory_get_child_files"""
777 _get_child_files = nmlib.notmuch_directory_get_child_files
778 _get_child_files.argtypes = [NotmuchDirectoryP]
779 _get_child_files.restype = NotmuchFilenamesP
781 """notmuch_directory_get_child_directories"""
782 _get_child_directories = nmlib.notmuch_directory_get_child_directories
783 _get_child_directories.argtypes = [NotmuchDirectoryP]
784 _get_child_directories.restype = NotmuchFilenamesP
786 def _assert_dir_is_initialized(self):
787 """Raises a NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
790 raise NotmuchError(STATUS.NOT_INITIALIZED)
792 def __init__(self, path, dir_p, parent):
794 :param path: The absolute path of the directory object as unicode.
795 :param dir_p: The pointer to an internal notmuch_directory_t object.
796 :param parent: The object this Directory is derived from
797 (usually a :class:`Database`). We do not directly use
798 this, but store a reference to it as long as
799 this Directory object lives. This keeps the
802 assert isinstance(path, unicode), "Path needs to be an UNICODE object"
805 self._parent = parent
807 def set_mtime(self, mtime):
808 """Sets the mtime value of this directory in the database
810 The intention is for the caller to use the mtime to allow efficient
811 identification of new messages to be added to the database. The
812 recommended usage is as follows:
814 * Read the mtime of a directory from the filesystem
816 * Call :meth:`Database.add_message` for all mail files in
819 * Call notmuch_directory_set_mtime with the mtime read from the
820 filesystem. Then, when wanting to check for updates to the
821 directory in the future, the client can call :meth:`get_mtime`
822 and know that it only needs to add files if the mtime of the
823 directory and files are newer than the stored timestamp.
827 :meth:`get_mtime` function does not allow the caller to
828 distinguish a timestamp of 0 from a non-existent timestamp. So
829 don't store a timestamp of 0 unless you are comfortable with
832 :param mtime: A (time_t) timestamp
833 :returns: Nothing on success, raising an exception on failure.
834 :exception: :exc:`NotmuchError`:
836 :attr:`STATUS`.XAPIAN_EXCEPTION
837 A Xapian exception occurred, mtime not stored.
838 :attr:`STATUS`.READ_ONLY_DATABASE
839 Database was opened in read-only mode so directory
840 mtime cannot be modified.
841 :attr:`STATUS`.NOT_INITIALIZED
842 The directory has not been initialized
844 self._assert_dir_is_initialized()
845 #TODO: make sure, we convert the mtime parameter to a 'c_long'
846 status = Directory._set_mtime(self._dir_p, mtime)
849 if status == STATUS.SUCCESS:
851 #fail with Exception otherwise
852 raise NotmuchError(status)
855 """Gets the mtime value of this directory in the database
857 Retrieves a previously stored mtime for this directory.
859 :param mtime: A (time_t) timestamp
860 :returns: Nothing on success, raising an exception on failure.
861 :exception: :exc:`NotmuchError`:
863 :attr:`STATUS`.NOT_INITIALIZED
864 The directory has not been initialized
866 self._assert_dir_is_initialized()
867 return Directory._get_mtime(self._dir_p)
869 # Make mtime attribute a property of Directory()
870 mtime = property(get_mtime, set_mtime, doc="""Property that allows getting
871 and setting of the Directory *mtime* (read-write)
873 See :meth:`get_mtime` and :meth:`set_mtime` for usage and
874 possible exceptions.""")
876 def get_child_files(self):
877 """Gets a Filenames iterator listing all the filenames of
878 messages in the database within the given directory.
880 The returned filenames will be the basename-entries only (not
883 self._assert_dir_is_initialized()
884 files_p = Directory._get_child_files(self._dir_p)
885 return Filenames(files_p, self)
887 def get_child_directories(self):
888 """Gets a :class:`Filenames` iterator listing all the filenames of
889 sub-directories in the database within the given directory
891 The returned filenames will be the basename-entries only (not
894 self._assert_dir_is_initialized()
895 files_p = Directory._get_child_directories(self._dir_p)
896 return Filenames(files_p, self)
900 """Returns the absolute path of this Directory (read-only)"""
904 """Object representation"""
905 return "<notmuch Directory object '%s'>" % self._path
907 _destroy = nmlib.notmuch_directory_destroy
908 _destroy.argtypes = [NotmuchDirectoryP]
909 _destroy.argtypes = None
912 """Close and free the Directory"""
913 if self._dir_p is not None:
914 self._destroy(self._dir_p)
917 class Filenames(object):
918 """An iterator over File- or Directory names stored in the database"""
920 #notmuch_filenames_get
921 _get = nmlib.notmuch_filenames_get
922 _get.argtypes = [NotmuchFilenamesP]
923 _get.restype = c_char_p
925 def __init__(self, files_p, parent):
927 :param files_p: The pointer to an internal notmuch_filenames_t object.
928 :param parent: The object this Directory is derived from
929 (usually a Directory()). We do not directly use
930 this, but store a reference to it as long as
931 this Directory object lives. This keeps the
934 self._files_p = files_p
935 self._parent = parent
938 """ Make Filenames an iterator """
941 _valid = nmlib.notmuch_filenames_valid
942 _valid.argtypes = [NotmuchFilenamesP]
943 _valid.restype = bool
945 _move_to_next = nmlib.notmuch_filenames_move_to_next
946 _move_to_next.argtypes = [NotmuchFilenamesP]
947 _move_to_next.restype = None
950 if not self._files_p:
951 raise NotmuchError(STATUS.NOT_INITIALIZED)
953 if not self._valid(self._files_p):
957 file_ = Filenames._get(self._files_p)
958 self._move_to_next(self._files_p)
959 return file_.decode('utf-8', 'ignore')
960 next = __next__ # python2.x iterator protocol compatibility
963 """len(:class:`Filenames`) returns the number of contained files
967 As this iterates over the files, we will not be able to
968 iterate over them again! So this will fail::
971 files = Database().get_directory('').get_child_files()
972 if len(files) > 0: # this 'exhausts' msgs
974 # NotmuchError(:attr:`STATUS`.NOT_INITIALIZED)
975 for file in files: print file
977 if not self._files_p:
978 raise NotmuchError(STATUS.NOT_INITIALIZED)
981 while self._valid(self._files_p):
982 self._move_to_next(self._files_p)
987 _destroy = nmlib.notmuch_filenames_destroy
988 _destroy.argtypes = [NotmuchFilenamesP]
989 _destroy.restype = None
992 """Close and free Filenames"""
993 if self._files_p is not None:
994 self._destroy(self._files_p)