From: Ludovic LANGE <ll-notmuch@lange.nom.fr>
To: notmuch@notmuchmail.org
Cc: Ludovic LANGE <ll-notmuch@lange.nom.fr>
Subject: [PATCH 1/1] python/notmuch2: provide binding for database_get_directory()
Date: Sun, 25 Jul 2021 10:16:02 +0200 [thread overview]
Message-ID: <20210725081602.81497-1-ll-notmuch@lange.nom.fr> (raw)
database_get_directory() is accessible in the legacy bindings as a method on the
database object. In the cffi bindings, it raises NotImplementedError, so we provide a
naive implementation, and the corresponding implementation of Directory object.
---
Hello,
This a my first try at updating the python-cffi bindings. The motivation for that is
related to `alot` which uses those bindings, and for which I'm missing the
notmuch_database_get_directory() call.
It may not be the cleanest implementation (I had to guess a few things) and I do not
have much experience with neither notmuch C API, nor python's CFFI. I'm waiting
for your comments in order to improve it, and hope it can be accepted.
Regards,
Ludovic.
bindings/python-cffi/notmuch2/_build.py | 18 +++
bindings/python-cffi/notmuch2/_database.py | 42 ++++-
bindings/python-cffi/notmuch2/_directory.py | 164 ++++++++++++++++++++
3 files changed, 223 insertions(+), 1 deletion(-)
create mode 100644 bindings/python-cffi/notmuch2/_directory.py
diff --git a/bindings/python-cffi/notmuch2/_build.py b/bindings/python-cffi/notmuch2/_build.py
index f712b6c5..0f0a0a46 100644
--- a/bindings/python-cffi/notmuch2/_build.py
+++ b/bindings/python-cffi/notmuch2/_build.py
@@ -134,6 +134,10 @@ ffibuilder.cdef(
notmuch_database_get_revision (notmuch_database_t *notmuch,
const char **uuid);
notmuch_status_t
+ notmuch_database_get_directory (notmuch_database_t *database,
+ const char *path,
+ notmuch_directory_t **directory);
+ notmuch_status_t
notmuch_database_index_file (notmuch_database_t *database,
const char *filename,
notmuch_indexopts_t *indexopts,
@@ -303,6 +307,20 @@ ffibuilder.cdef(
void
notmuch_tags_destroy (notmuch_tags_t *tags);
+ notmuch_status_t
+ notmuch_directory_set_mtime (notmuch_directory_t *directory,
+ time_t mtime);
+ time_t
+ notmuch_directory_get_mtime (notmuch_directory_t *directory);
+ notmuch_filenames_t *
+ notmuch_directory_get_child_files (notmuch_directory_t *directory);
+ notmuch_filenames_t *
+ notmuch_directory_get_child_directories (notmuch_directory_t *directory);
+ notmuch_status_t
+ notmuch_directory_delete (notmuch_directory_t *directory);
+ void
+ notmuch_directory_destroy (notmuch_directory_t *directory);
+
notmuch_bool_t
notmuch_filenames_valid (notmuch_filenames_t *filenames);
const char *
diff --git a/bindings/python-cffi/notmuch2/_database.py b/bindings/python-cffi/notmuch2/_database.py
index 868f4408..e48fa895 100644
--- a/bindings/python-cffi/notmuch2/_database.py
+++ b/bindings/python-cffi/notmuch2/_database.py
@@ -13,6 +13,7 @@ import notmuch2._errors as errors
import notmuch2._message as message
import notmuch2._query as querymod
import notmuch2._tags as tags
+import notmuch2._directory as directory
__all__ = ['Database', 'AtomicContext', 'DbRevision']
@@ -338,7 +339,46 @@ class Database(base.NotmuchObject):
return DbRevision(rev, capi.ffi.string(raw_uuid[0]))
def get_directory(self, path):
- raise NotImplementedError
+ """Returns a :class:`Directory` from the database for path,
+
+ :param path: An unicode string containing the path relative to the path
+ of database (see :attr:`path`), or else should be an absolute
+ path with initial components that match the path of 'database'.
+ :returns: :class:`Directory` or raises an exception.
+ :raises: :exc:`FileError` if path is not relative database or absolute
+ with initial components same as database.
+
+
+ :raises XapianError: A Xapian exception occurred.
+ :raises LookupError: The directory object referred to by ``pathname``
+ does not exist in the database.
+ :raises FileNotEmailError: The file referreed to by
+ ``pathname`` is not recognised as an email message.
+ :raises UpgradeRequiredError: The database must be upgraded
+ first.
+ """
+ if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
+ path = bytes(path)
+ directory_pp = capi.ffi.new('notmuch_directory_t **')
+ ret = capi.lib.notmuch_database_get_directory(
+ self._db_p, os.fsencode(path), directory_pp)
+
+ if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+ raise errors.NotmuchError(ret)
+
+ directory_p = directory_pp[0]
+ if directory_p == capi.ffi.NULL:
+ raise LookupError
+
+ if os.path.isabs(path):
+ # we got an absolute path
+ abs_dirpath = path
+ else:
+ #we got a relative path, make it absolute
+ abs_dirpath = os.path.abspath(os.path.join(self.get_path(), path))
+
+ ret_dir = directory.Directory(abs_dirpath, directory_p, self)
+ return ret_dir
def default_indexopts(self):
"""Returns default index options for the database.
diff --git a/bindings/python-cffi/notmuch2/_directory.py b/bindings/python-cffi/notmuch2/_directory.py
new file mode 100644
index 00000000..1d48aa54
--- /dev/null
+++ b/bindings/python-cffi/notmuch2/_directory.py
@@ -0,0 +1,164 @@
+import os
+import pathlib
+
+import notmuch2._base as base
+import notmuch2._capi as capi
+import notmuch2._errors as errors
+from ._message import FilenamesIter
+
+__all__ = ["Directory"]
+
+
+class PurePathIter(FilenamesIter):
+ """Iterator for pathlib.PurePath objects."""
+
+ def __next__(self):
+ fname = super().__next__()
+ return pathlib.PurePath(os.fsdecode(fname))
+
+
+class Directory(base.NotmuchObject):
+ """Represents a directory entry in the notmuch directory
+
+ Modifying attributes of this object will modify the
+ database, not the real directory attributes.
+
+ The Directory object is usually derived from another object
+ e.g. via :meth:`Database.get_directory`, and will automatically be
+ become invalid whenever that parent is deleted. You should
+ therefore initialized this object handing it a reference to the
+ parent, preventing the parent from automatically being garbage
+ collected.
+ """
+
+ _msg_p = base.MemoryPointer()
+
+ def __init__(self, path, dir_p, parent):
+ """
+ :param path: The absolute path of the directory object.
+ :param dir_p: The pointer to an internal notmuch_directory_t object.
+ :param parent: The object this Directory is derived from
+ (usually a :class:`Database`). We do not directly use
+ this, but store a reference to it as long as
+ this Directory object lives. This keeps the
+ parent object alive.
+ """
+ self._path = path
+ self._dir_p = dir_p
+ self._parent = parent
+
+ @property
+ def alive(self):
+ if not self._parent.alive:
+ return False
+ try:
+ self._dir_p
+ except errors.ObjectDestroyedError:
+ return False
+ else:
+ return True
+
+ def __del__(self):
+ """Close and free the Directory"""
+ self._destroy()
+
+ def _destroy(self):
+ if self.alive:
+ capi.lib.notmuch_directory_destroy(self._dir_p)
+ self._dir_p = None
+
+ def set_mtime(self, mtime):
+ """Sets the mtime value of this directory in the database
+
+ The intention is for the caller to use the mtime to allow efficient
+ identification of new messages to be added to the database. The
+ recommended usage is as follows:
+
+ * Read the mtime of a directory from the filesystem
+
+ * Call :meth:`Database.index_file` for all mail files in
+ the directory
+
+ * Call notmuch_directory_set_mtime with the mtime read from the
+ filesystem. Then, when wanting to check for updates to the
+ directory in the future, the client can call :meth:`get_mtime`
+ and know that it only needs to add files if the mtime of the
+ directory and files are newer than the stored timestamp.
+
+ .. note::
+
+ :meth:`get_mtime` function does not allow the caller to
+ distinguish a timestamp of 0 from a non-existent timestamp. So
+ don't store a timestamp of 0 unless you are comfortable with
+ that.
+
+ :param mtime: A (time_t) timestamp
+ :raises: :exc:`XapianError` a Xapian exception occurred, mtime
+ not stored
+ :raises: :exc:`ReadOnlyDatabaseError` the database was opened
+ in read-only mode so directory mtime cannot be modified
+ :raises: :exc:`NotInitializedError` the directory object has not
+ been initialized
+ """
+ ret = capi.lib.notmuch_directory_set_mtime(self._dir_p, mtime)
+
+ if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
+ raise errors.NotmuchError(ret)
+
+ def get_mtime(self):
+ """Gets the mtime value of this directory in the database
+
+ Retrieves a previously stored mtime for this directory.
+
+ :param mtime: A (time_t) timestamp
+ :raises: :exc:`NotmuchError`:
+
+ :attr:`STATUS`.NOT_INITIALIZED
+ The directory has not been initialized
+ """
+ return capi.lib.notmuch_directory_get_mtime(self._dir_p)
+
+ # Make mtime attribute a property of Directory()
+ mtime = property(
+ get_mtime,
+ set_mtime,
+ doc="""Property that allows getting
+ and setting of the Directory *mtime* (read-write)
+
+ See :meth:`get_mtime` and :meth:`set_mtime` for usage and
+ possible exceptions.""",
+ )
+
+ def get_child_files(self):
+ """Gets a Filenames iterator listing all the filenames of
+ messages in the database within the given directory.
+
+ The returned filenames will be the basename-entries only (not
+ complete paths.
+ """
+ fnames_p = capi.lib.notmuch_directory_get_child_files(self._dir_p)
+ return PurePathIter(self, fnames_p)
+
+ def get_child_directories(self):
+ """Gets a :class:`Filenames` iterator listing all the filenames of
+ sub-directories in the database within the given directory
+
+ The returned filenames will be the basename-entries only (not
+ complete paths.
+ """
+ fnames_p = capi.lib.notmuch_directory_get_child_directories(self._dir_p)
+ return PurePathIter(self, fnames_p)
+
+ @property
+ def path(self):
+ """Returns the absolute path of this Directory (read-only)"""
+ return self._path
+
+ def __repr__(self):
+ """Object representation"""
+ try:
+ self._dir_p
+ except errors.ObjectDestroyedError:
+ return '<Directory(path={self.path}) (exhausted)>'.format(self=self)
+ else:
+ return '<Directory(path={self.path})>'.format(self=self)
--
2.32.0
next reply other threads:[~2021-07-25 8:26 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-07-25 8:16 Ludovic LANGE [this message]
2021-07-25 20:18 ` [PATCH 1/1] python/notmuch2: provide binding for database_get_directory() Floris Bruynooghe
2021-07-25 20:46 ` Ludovic LANGE
2021-07-26 7:55 ` Tomi Ollila
[not found] ` <87im0v31z8.fsf@powell.devork.be>
[not found] ` <df94c8da-eba7-f420-8b9a-80b0f6b8a419@lange.nom.fr>
2021-07-31 9:20 ` Floris Bruynooghe
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://notmuchmail.org/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210725081602.81497-1-ll-notmuch@lange.nom.fr \
--to=ll-notmuch@lange.nom.fr \
--cc=notmuch@notmuchmail.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://yhetil.org/notmuch.git/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).