-import ctypes
+import ctypes, os
from ctypes import c_int, c_char_p, c_void_p, c_uint, c_uint64, c_bool
from cnotmuch.globals import nmlib, STATUS, NotmuchError, Enum
import logging
as well. Accessing these objects will lead to segfaults and
other unexpected behavior. See above for more details.
"""
- MODE = Enum(['READ_ONLY','READ_WRITE'])
- """Constants: Mode in which to open the database"""
-
_std_db_path = None
"""Class attribute to cache user's default database"""
+ MODE = Enum(['READ_ONLY','READ_WRITE'])
+ """Constants: Mode in which to open the database"""
+
"""notmuch_database_get_path (notmuch_database_t *database)"""
_get_path = nmlib.notmuch_database_get_path
_get_path.restype = c_char_p
_create = nmlib.notmuch_database_create
_create.restype = c_void_p
- def __init__(self, path=None, create=False, mode= MODE.READ_ONLY):
- """If *path* is *None*, we will try to read a users notmuch
- configuration and use his default database. If *create* is `True`,
- the database will always be created in
- :attr:`MODE`.READ_WRITE mode.
+ def __init__(self, path=None, create=False, mode= 0):
+ """If *path* is *None*, we will try to read a users notmuch
+ configuration and use his configured database. The location of the
+ configuration file can be specified through the environment variable
+ *NOTMUCH_CONFIG*, falling back to the default `~/.notmuch-config`.
+
+ If *create* is `True`, the database will always be created in
+ :attr:`MODE`.READ_WRITE mode. Default mode for opening is READ_ONLY.
:param path: Directory to open/create the database in (see
above for behavior if `None`)
raise NotmuchError(
message="Cannot create db, this Database() already has an open one.")
- res = Database._create(path, MODE.READ_WRITE)
+ res = Database._create(path, Database.MODE.READ_WRITE)
if res is None:
raise NotmuchError(
message="Could not create the specified database")
self._db = res
- def open(self, path, mode= MODE.READ_ONLY):
+ def open(self, path, mode= 0):
"""Opens an existing database
This function is used by __init__() and usually does not need
def needs_upgrade(self):
"""Does this database need to be upgraded before writing to it?
- If this function returns TRUE then no functions that modify the
- database (:meth:`Database.add_message`, :meth:`Database.add_tag`,
+ If this function returns True then no functions that modify the
+ database (:meth:`add_message`, :meth:`add_tag`,
:meth:`Directory.set_mtime`, etc.) will work unless :meth:`upgrade`
is called successfully first.
Throws a NotmuchError if it cannot find it"""
from ConfigParser import SafeConfigParser
- import os.path
config = SafeConfigParser()
- config.read(os.path.expanduser('~/.notmuch-config'))
+ conf_f = os.getenv('NOTMUCH_CONFIG',
+ os.path.expanduser('~/.notmuch-config'))
+ config.read(conf_f)
if not config.has_option('database','path'):
raise NotmuchError(message=
"No DB path specified and no user default found")
_search_messages = nmlib.notmuch_query_search_messages
_search_messages.restype = c_void_p
+
+ """notmuch_query_count_messages"""
+ _count_messages = nmlib.notmuch_query_count_messages
+ _count_messages.restype = c_uint
+
def __init__(self, db, querystr):
"""
:param db: An open database which we derive the Query from.
return Messages(msgs_p,self)
+ def count_messages(self):
+ """Estimate the number of messages matching the query
+
+ This function performs a search and returns Xapian's best
+ guess as to the number of matching messages. It is much faster
+ than performing :meth:`search_messages` and counting the
+ result with `len()` (although it always returned the same
+ result in my tests). Technically, it wraps the underlying
+ *notmuch_query_count_messages* function.
+
+ :returns: :class:`Messages`
+ :exception: :exc:`NotmuchError`
+
+ * STATUS.NOT_INITIALIZED if query is not inited
+ """
+ if self._query is None:
+ raise NotmuchError(STATUS.NOT_INITIALIZED)
+
+ return Query._count_messages(self._query)
def __del__(self):
"""Close and free the Query"""
STATUS.NULL_POINTER
The 'tag' argument is NULL
-
STATUS.TAG_TOO_LONG
The length of 'tag' is too long
(exceeds Message.NOTMUCH_TAG_MAX)
-
STATUS.READ_ONLY_DATABASE
Database was opened in read-only mode so message cannot be
modified.
-
STATUS.NOT_INITIALIZED
The message has not been initialized.
"""
STATUS.NULL_POINTER
The 'tag' argument is NULL
- NOTMUCH_STATUS_TAG_TOO_LONG
+ STATUS.TAG_TOO_LONG
The length of 'tag' is too long
(exceeds NOTMUCH_TAG_MAX)
- NOTMUCH_STATUS_READ_ONLY_DATABASE
+ STATUS.READ_ONLY_DATABASE
Database was opened in read-only mode so message cannot
be modified.
STATUS.NOT_INITIALIZED
The message has not been initialized.
-
"""
if self._msg is None:
raise NotmuchError(STATUS.NOT_INITIALIZED)
raise NotmuchError(status)
+ def remove_all_tags(self):
+ """Removes all tags from the given message.
+
+ See :meth:`freeze` for an example showing how to safely
+ replace tag values.
+
+ :returns: STATUS.SUCCESS if the tags were successfully removed.
+ Raises an exception otherwise.
+ :exception: :exc:`NotmuchError`. They have the following meaning:
+
+ STATUS.READ_ONLY_DATABASE
+ Database was opened in read-only mode so message cannot
+ be modified.
+ STATUS.NOT_INITIALIZED
+ The message has not been initialized.
+ """
+ if self._msg is None:
+ raise NotmuchError(STATUS.NOT_INITIALIZED)
+
+ status = nmlib.notmuch_message_remove_all_tags(self._msg)
+
+ if STATUS.SUCCESS == status:
+ # return on success
+ return status
+
+ raise NotmuchError(status)
+
+ def freeze(self):
+ """Freezes the current state of 'message' within the database
+
+ This means that changes to the message state, (via :meth:`add_tag`,
+ :meth:`remove_tag`, and :meth:`remove_all_tags`), will not be
+ committed to the database until the message is :meth:`thaw`ed.
+
+ Multiple calls to freeze/thaw are valid and these calls will
+ "stack". That is there must be as many calls to thaw as to freeze
+ before a message is actually thawed.
+
+ The ability to do freeze/thaw allows for safe transactions to
+ change tag values. For example, explicitly setting a message to
+ have a given set of tags might look like this::
+
+ msg.freeze()
+ msg.remove_all_tags()
+ for tag in new_tags:
+ msg.add_tag(tag)
+ msg.thaw()
+
+ With freeze/thaw used like this, the message in the database is
+ guaranteed to have either the full set of original tag values, or
+ the full set of new tag values, but nothing in between.
+
+ Imagine the example above without freeze/thaw and the operation
+ somehow getting interrupted. This could result in the message being
+ left with no tags if the interruption happened after
+ :meth:`remove_all_tags` but before :meth:`add_tag`.
+
+ :returns: STATUS.SUCCESS if the message was successfully frozen.
+ Raises an exception otherwise.
+ :exception: :exc:`NotmuchError`. They have the following meaning:
+
+ STATUS.READ_ONLY_DATABASE
+ Database was opened in read-only mode so message cannot
+ be modified.
+ STATUS.NOT_INITIALIZED
+ The message has not been initialized.
+ """
+ if self._msg is None:
+ raise NotmuchError(STATUS.NOT_INITIALIZED)
+
+ status = nmlib.notmuch_message_freeze(self._msg)
+
+ if STATUS.SUCCESS == status:
+ # return on success
+ return status
+
+ raise NotmuchError(status)
+
+ def thaw(self):
+ """Thaws the current 'message'
+
+ Thaw the current 'message', synchronizing any changes that may have
+ occurred while 'message' was frozen into the notmuch database.
+
+ See :meth:`freeze` for an example of how to use this
+ function to safely provide tag changes.
+
+ Multiple calls to freeze/thaw are valid and these calls with
+ "stack". That is there must be as many calls to thaw as to freeze
+ before a message is actually thawed.
+
+ :returns: STATUS.SUCCESS if the message was successfully frozen.
+ Raises an exception otherwise.
+ :exception: :exc:`NotmuchError`. They have the following meaning:
+
+ STATUS.UNBALANCED_FREEZE_THAW
+ An attempt was made to thaw an unfrozen message.
+ That is, there have been an unbalanced number of calls
+ to :meth:`freeze` and :meth:`thaw`.
+ STATUS.NOT_INITIALIZED
+ The message has not been initialized.
+ """
+ if self._msg is None:
+ raise NotmuchError(STATUS.NOT_INITIALIZED)
+
+ status = nmlib.notmuch_message_thaw(self._msg)
+
+ if STATUS.SUCCESS == status:
+ # return on success
+ return status
+
+ raise NotmuchError(status)
+
+
def __str__(self):
"""A message() is represented by a 1-line summary"""
msg = {}