X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=bindings%2Fpython%2Fnotmuch%2Fdatabase.py;h=88ca836e2d1aa4c78471ede28f7541fac630aafa;hp=5931f41bc26c28d17ca1b5b306beb9abef313674;hb=7eb9615b30274033cc0c828244569c709906c40b;hpb=d99f15d7ee38d606a2b3b5bd34f1e6c787b3b8a2 diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 5931f41b..88ca836e 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -12,21 +12,24 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with notmuch. If not, see . +along with notmuch. If not, see . Copyright 2010 Sebastian Spaeth """ import os import codecs +import warnings from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER from .compat import SafeConfigParser from .globals import ( nmlib, Enum, _str, + NotmuchConfigListP, NotmuchDatabaseP, NotmuchDirectoryP, + NotmuchIndexoptsP, NotmuchMessageP, NotmuchTagsP, ) @@ -36,7 +39,6 @@ from .errors import ( NotmuchError, NullPointerError, NotInitializedError, - ReadOnlyDatabaseError, ) from .message import Message from .tag import Tags @@ -72,6 +74,9 @@ class Database(object): MODE = Enum(['READ_ONLY', 'READ_WRITE']) """Constants: Mode in which to open the database""" + DECRYPTION_POLICY = Enum(['FALSE', 'TRUE', 'AUTO', 'NOSTASH']) + """Constants: policies for decrypting messages during indexing""" + """notmuch_database_get_directory""" _get_directory = nmlib.notmuch_database_get_directory _get_directory.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(NotmuchDirectoryP)] @@ -87,6 +92,11 @@ class Database(object): _get_version.argtypes = [NotmuchDatabaseP] _get_version.restype = c_uint + """notmuch_database_get_revision""" + _get_revision = nmlib.notmuch_database_get_revision + _get_revision.argtypes = [NotmuchDatabaseP, POINTER(c_char_p)] + _get_revision.restype = c_uint + """notmuch_database_open""" _open = nmlib.notmuch_database_open _open.argtypes = [c_char_p, c_uint, POINTER(NotmuchDatabaseP)] @@ -157,11 +167,13 @@ class Database(object): _destroy = nmlib.notmuch_database_destroy _destroy.argtypes = [NotmuchDatabaseP] - _destroy.restype = None + _destroy.restype = c_uint def __del__(self): if self._db: - self._destroy(self._db) + status = self._destroy(self._db) + if status != STATUS.SUCCESS: + raise NotmuchError(status) def _assert_db_is_initialized(self): """Raises :exc:`NotInitializedError` if self._db is `None`""" @@ -188,7 +200,7 @@ class Database(object): "already has an open one.") db = NotmuchDatabaseP() - status = Database._create(_str(path), Database.MODE.READ_WRITE, byref(db)) + status = Database._create(_str(path), byref(db)) if status != STATUS.SUCCESS: raise NotmuchError(status) @@ -217,7 +229,7 @@ class Database(object): _close = nmlib.notmuch_database_close _close.argtypes = [NotmuchDatabaseP] - _close.restype = None + _close.restype = c_uint def close(self): ''' @@ -231,7 +243,9 @@ class Database(object): NotmuchError. ''' if self._db: - self._close(self._db) + status = self._close(self._db) + if status != STATUS.SUCCESS: + raise NotmuchError(status) def __enter__(self): ''' @@ -258,6 +272,17 @@ class Database(object): self._assert_db_is_initialized() return Database._get_version(self._db) + def get_revision (self): + """Returns the committed database revison and UUID + + :returns: (revison, uuid) The database revision as a positive integer + and the UUID of the database. + """ + self._assert_db_is_initialized() + uuid = c_char_p () + revision = Database._get_revision(self._db, byref (uuid)) + return (revision, uuid.value.decode ('utf-8')) + _needs_upgrade = nmlib.notmuch_database_needs_upgrade _needs_upgrade.argtypes = [NotmuchDatabaseP] _needs_upgrade.restype = bool @@ -266,7 +291,7 @@ class Database(object): """Does this database need to be upgraded before writing to it? If this function returns `True` then no functions that modify the - database (:meth:`add_message`, + database (:meth:`index_file`, :meth:`Message.add_tag`, :meth:`Directory.set_mtime`, etc.) will work unless :meth:`upgrade` is called successfully first. @@ -292,7 +317,7 @@ class Database(object): """ self._assert_db_is_initialized() status = Database._upgrade(self._db, None, None) - #TODO: catch exceptions, document return values and etc + # TODO: catch exceptions, document return values and etc return status _begin_atomic = nmlib.notmuch_database_begin_atomic @@ -380,12 +405,25 @@ class Database(object): # return the Directory, init it with the absolute path return Directory(abs_dirpath, dir_p, self) - _add_message = nmlib.notmuch_database_add_message - _add_message.argtypes = [NotmuchDatabaseP, c_char_p, + _get_default_indexopts = nmlib.notmuch_database_get_default_indexopts + _get_default_indexopts.argtypes = [NotmuchDatabaseP] + _get_default_indexopts.restype = NotmuchIndexoptsP + + _indexopts_set_decrypt_policy = nmlib.notmuch_indexopts_set_decrypt_policy + _indexopts_set_decrypt_policy.argtypes = [NotmuchIndexoptsP, c_uint] + _indexopts_set_decrypt_policy.restype = None + + _indexopts_destroy = nmlib.notmuch_indexopts_destroy + _indexopts_destroy.argtypes = [NotmuchIndexoptsP] + _indexopts_destroy.restype = None + + _index_file = nmlib.notmuch_database_index_file + _index_file.argtypes = [NotmuchDatabaseP, c_char_p, + c_void_p, POINTER(NotmuchMessageP)] - _add_message.restype = c_uint + _index_file.restype = c_uint - def add_message(self, filename, sync_maildir_flags=False): + def index_file(self, filename, sync_maildir_flags=False, decrypt_policy=None): """Adds a new message to the database :param filename: should be a path relative to the path of the @@ -406,6 +444,23 @@ class Database(object): API. You might want to look into the underlying method :meth:`Message.maildir_flags_to_tags`. + :param decrypt_policy: If the message contains any encrypted + parts, and decrypt_policy is set to + :attr:`DECRYPTION_POLICY`.TRUE, notmuch will try to + decrypt the message and index the cleartext, stashing any + discovered session keys. If it is set to + :attr:`DECRYPTION_POLICY`.FALSE, it will never try to + decrypt during indexing. If it is set to + :attr:`DECRYPTION_POLICY`.AUTO, then it will try to use + any stashed session keys it knows about, but will not try + to access the user's secret keys. + :attr:`DECRYPTION_POLICY`.NOSTASH behaves the same as + :attr:`DECRYPTION_POLICY`.TRUE except that no session keys + are stashed in the database. If decrypt_policy is set to + None (the default), then the database itself will decide + whether to decrypt, based on the `index.decrypt` + configuration setting (see notmuch-config(1)). + :returns: On success, we return 1) a :class:`Message` object that can be used for things @@ -436,7 +491,15 @@ class Database(object): """ self._assert_db_is_initialized() msg_p = NotmuchMessageP() - status = self._add_message(self._db, _str(filename), byref(msg_p)) + indexopts = c_void_p(None) + if decrypt_policy is not None: + indexopts = self._get_default_indexopts(self._db) + self._indexopts_set_decrypt_policy(indexopts, decrypt_policy) + + status = self._index_file(self._db, _str(filename), indexopts, byref(msg_p)) + + if indexopts: + self._indexopts_destroy(indexopts) if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]: raise NotmuchError(status) @@ -448,6 +511,14 @@ class Database(object): msg.maildir_flags_to_tags() return (msg, status) + def add_message(self, filename, sync_maildir_flags=False): + """Deprecated alias for :meth:`index_file` + """ + warnings.warn( + "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning) + + return self.index_file(filename, sync_maildir_flags=sync_maildir_flags) + _remove_message = nmlib.notmuch_database_remove_message _remove_message.argtypes = [NotmuchDatabaseP, c_char_p] _remove_message.restype = c_uint @@ -480,7 +551,10 @@ class Database(object): removed. """ self._assert_db_is_initialized() - return self._remove_message(self._db, _str(filename)) + status = self._remove_message(self._db, _str(filename)) + if status not in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]: + raise NotmuchError(status) + return status def find_message(self, msgid): """Returns a :class:`Message` as identified by its message ID @@ -492,7 +566,7 @@ class Database(object): :returns: :class:`Message` or `None` if no message is found. :raises: :exc:`OutOfMemoryError` - If an Out-of-memory occured while constructing the message. + If an Out-of-memory occurred while constructing the message. :exc:`XapianError` In case of a Xapian Exception. These exceptions include "Database modified" situations, e.g. when the @@ -517,7 +591,7 @@ class Database(object): function returns None if no message is found with the given filename. - :raises: :exc:`OutOfMemoryError` if an Out-of-memory occured while + :raises: :exc:`OutOfMemoryError` if an Out-of-memory occurred while constructing the message. :raises: :exc:`XapianError` in case of a Xapian Exception. These exceptions include "Database modified" @@ -571,6 +645,22 @@ class Database(object): """ return Query(self, querystring) + """notmuch_database_status_string""" + _status_string = nmlib.notmuch_database_status_string + _status_string.argtypes = [NotmuchDatabaseP] + _status_string.restype = c_char_p + + def status_string(self): + """Returns the status string of the database + + This is sometimes used for additional error reporting + """ + self._assert_db_is_initialized() + s = Database._status_string(self._db) + if s: + return s.decode('utf-8', 'ignore') + return s + def __repr__(self): return "'Notmuch DB " + self.get_path() + "'" @@ -585,15 +675,115 @@ class Database(object): if not config.has_option('database', 'path'): raise NotmuchError(message="No DB path specified" " and no user default found") - return config.get('database', 'path') + db_path = config.get('database', 'path') + if not os.path.isabs(db_path): + db_path = os.path.expanduser(os.path.join("~", db_path)) + return db_path + + """notmuch_database_get_config""" + _get_config = nmlib.notmuch_database_get_config + _get_config.argtypes = [NotmuchDatabaseP, c_char_p, POINTER(c_char_p)] + _get_config.restype = c_uint + + def get_config(self, key): + """Return the value of the given config key. + + Note that only config values that are stored in the database are + searched and returned. The config file is not read. + + :param key: the config key under which a value should be looked up, it + should probably be in the form "section.key" + :type key: str + :returns: the config value or the empty string if no value is present + for that key + :rtype: str + :raises: :exc:`NotmuchError` in case of failure. - @property - def db_p(self): - """Property returning a pointer to `notmuch_database_t` or `None` + """ + self._assert_db_is_initialized() + return_string = c_char_p() + status = self._get_config(self._db, _str(key), byref(return_string)) + if status != STATUS.SUCCESS: + raise NotmuchError(status) + return return_string.value.decode('utf-8') + + """notmuch_database_get_config_list""" + _get_config_list = nmlib.notmuch_database_get_config_list + _get_config_list.argtypes = [NotmuchDatabaseP, c_char_p, + POINTER(NotmuchConfigListP)] + _get_config_list.restype = c_uint + + _config_list_valid = nmlib.notmuch_config_list_valid + _config_list_valid.argtypes = [NotmuchConfigListP] + _config_list_valid.restype = bool + + _config_list_key = nmlib.notmuch_config_list_key + _config_list_key.argtypes = [NotmuchConfigListP] + _config_list_key.restype = c_char_p + + _config_list_value = nmlib.notmuch_config_list_value + _config_list_value.argtypes = [NotmuchConfigListP] + _config_list_value.restype = c_char_p + + _config_list_move_to_next = nmlib.notmuch_config_list_move_to_next + _config_list_move_to_next.argtypes = [NotmuchConfigListP] + _config_list_move_to_next.restype = None + + _config_list_destroy = nmlib.notmuch_config_list_destroy + _config_list_destroy.argtypes = [NotmuchConfigListP] + _config_list_destroy.restype = None + + def get_configs(self, prefix=''): + """Return a generator of key, value pairs where the start of key + matches the given prefix + + Note that only config values that are stored in the database are + searched and returned. The config file is not read. If no `prefix` is + given all config values are returned. + + This could be used to get all named queries into a dict for example:: + + queries = {k[6:]: v for k, v in db.get_configs('query.')} + + :param prefix: a string by which the keys should be selected + :type prefix: str + :yields: all key-value pairs where `prefix` matches the beginning + of the key + :ytype: pairs of str + :raises: :exc:`NotmuchError` in case of failure. - .. deprecated:: 0.14 - If you really need a pointer to the notmuch - database object use the `_pointer` field. This - alias will be removed in notmuch 0.15. """ - return self._db + self._assert_db_is_initialized() + config_list_p = NotmuchConfigListP() + status = self._get_config_list(self._db, _str(prefix), + byref(config_list_p)) + if status != STATUS.SUCCESS: + raise NotmuchError(status) + while self._config_list_valid(config_list_p): + key = self._config_list_key(config_list_p).decode('utf-8') + value = self._config_list_value(config_list_p).decode('utf-8') + yield key, value + self._config_list_move_to_next(config_list_p) + + """notmuch_database_set_config""" + _set_config = nmlib.notmuch_database_set_config + _set_config.argtypes = [NotmuchDatabaseP, c_char_p, c_char_p] + _set_config.restype = c_uint + + def set_config(self, key, value): + """Set a config value in the notmuch database. + + If an empty string is provided as `value` the `key` is unset! + + :param key: the key to set + :type key: str + :param value: the value to store under `key` + :type value: str + :returns: None + :raises: :exc:`NotmuchError` in case of failure. + + """ + self._assert_db_is_initialized() + status = self._set_config(self._db, _str(key), _str(value)) + if status != STATUS.SUCCESS: + raise NotmuchError(status)