Merge branch 'release'

This commit is contained in:
David Bremner 2021-12-04 09:27:30 -04:00
commit 1e7d33961e
12 changed files with 198 additions and 57 deletions

View file

@ -103,21 +103,19 @@ ffibuilder.cdef(
notmuch_status_to_string (notmuch_status_t status);
notmuch_status_t
notmuch_database_create_verbose (const char *path,
notmuch_database_create_with_config (const char *database_path,
const char *config_path,
const char *profile,
notmuch_database_t **database,
char **error_message);
notmuch_status_t
notmuch_database_create (const char *path, notmuch_database_t **database);
notmuch_status_t
notmuch_database_open_verbose (const char *path,
notmuch_database_open_with_config (const char *database_path,
notmuch_database_mode_t mode,
const char *config_path,
const char *profile,
notmuch_database_t **database,
char **error_message);
notmuch_status_t
notmuch_database_open (const char *path,
notmuch_database_mode_t mode,
notmuch_database_t **database);
notmuch_status_t
notmuch_database_close (notmuch_database_t *database);
notmuch_status_t
notmuch_database_destroy (notmuch_database_t *database);

View file

@ -31,6 +31,9 @@ class Mode(enum.Enum):
READ_ONLY = capi.lib.NOTMUCH_DATABASE_MODE_READ_ONLY
READ_WRITE = capi.lib.NOTMUCH_DATABASE_MODE_READ_WRITE
class ConfigFile(enum.Enum):
EMPTY = b''
SEARCH = capi.ffi.NULL
class QuerySortOrder(enum.Enum):
OLDEST_FIRST = capi.lib.NOTMUCH_SORT_OLDEST_FIRST
@ -71,6 +74,9 @@ class Database(base.NotmuchObject):
:cvar EXCLUDE: Which messages to exclude from queries, ``TRUE``,
``FLAG``, ``FALSE`` or ``ALL``. See the query documentation
for details.
:cvar CONFIG: Control loading of config file. Enumeration of
``EMPTY`` (don't load a config file), and ``SEARCH`` (search as
in :ref:`config_search`)
:cvar AddedMessage: A namedtuple ``(msg, dup)`` used by
:meth:`add` as return value.
:cvar STR_MODE_MAP: A map mapping strings to :attr:`MODE` items.
@ -81,9 +87,8 @@ class Database(base.NotmuchObject):
still open.
:param path: The directory of where the database is stored. If
``None`` the location will be read from the user's
configuration file, respecting the ``NOTMUCH_CONFIG``
environment variable if set.
``None`` the location will be searched according to
:ref:`database`
:type path: str, bytes, os.PathLike or pathlib.Path
:param mode: The mode to open the database in. One of
:attr:`MODE.READ_ONLY` OR :attr:`MODE.READ_WRITE`. For
@ -91,6 +96,8 @@ class Database(base.NotmuchObject):
:attr:`MODE.READ_ONLY` and ``rw`` for :attr:`MODE.READ_WRITE`.
:type mode: :attr:`MODE` or str.
:param config: Where to load the configuration from, if any.
:type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
:raises KeyError: if an unknown mode string is used.
:raises OSError: or subclasses if the configuration file can not
be opened.
@ -102,6 +109,7 @@ class Database(base.NotmuchObject):
MODE = Mode
SORT = QuerySortOrder
EXCLUDE = QueryExclude
CONFIG = ConfigFile
AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup'])
_db_p = base.MemoryPointer()
STR_MODE_MAP = {
@ -109,18 +117,40 @@ class Database(base.NotmuchObject):
'rw': MODE.READ_WRITE,
}
def __init__(self, path=None, mode=MODE.READ_ONLY):
@staticmethod
def _cfg_path_encode(path):
if isinstance(path,ConfigFile):
path = path.value
elif path is None:
path = capi.ffi.NULL
elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
else:
path = os.fsencode(path)
return path
@staticmethod
def _db_path_encode(path):
if path is None:
path = capi.ffi.NULL
elif not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
else:
path = os.fsencode(path)
return path
def __init__(self, path=None, mode=MODE.READ_ONLY, config=CONFIG.EMPTY):
if isinstance(mode, str):
mode = self.STR_MODE_MAP[mode]
self.mode = mode
if path is None:
path = self.default_path()
if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
db_pp = capi.ffi.new('notmuch_database_t **')
cmsg = capi.ffi.new('char**')
ret = capi.lib.notmuch_database_open_verbose(os.fsencode(path),
mode.value, db_pp, cmsg)
ret = capi.lib.notmuch_database_open_with_config(self._db_path_encode(path),
mode.value,
self._cfg_path_encode(config),
capi.ffi.NULL,
db_pp, cmsg)
if cmsg[0]:
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
capi.lib.free(cmsg[0])
@ -132,18 +162,20 @@ class Database(base.NotmuchObject):
self.closed = False
@classmethod
def create(cls, path=None):
def create(cls, path=None, config=ConfigFile.EMPTY):
"""Create and open database in READ_WRITE mode.
This is creates a new notmuch database and returns an opened
instance in :attr:`MODE.READ_WRITE` mode.
:param path: The directory of where the database is stored. If
``None`` the location will be read from the user's
configuration file, respecting the ``NOTMUCH_CONFIG``
environment variable if set.
:param path: The directory of where the database is stored.
If ``None`` the location will be read searched by the
notmuch library (see notmuch(3)::notmuch_open_with_config).
:type path: str, bytes or os.PathLike
:param config: The pathname of the notmuch configuration file.
:type config: :attr:`CONFIG.EMPTY`, :attr:`CONFIG.SEARCH`, str, bytes, os.PathLike, pathlib.Path
:raises OSError: or subclasses if the configuration file can not
be opened.
:raises configparser.Error: or subclasses if the configuration
@ -154,13 +186,12 @@ class Database(base.NotmuchObject):
:returns: The newly created instance.
"""
if path is None:
path = cls.default_path()
if not hasattr(os, 'PathLike') and isinstance(path, pathlib.Path):
path = bytes(path)
db_pp = capi.ffi.new('notmuch_database_t **')
cmsg = capi.ffi.new('char**')
ret = capi.lib.notmuch_database_create_verbose(os.fsencode(path),
ret = capi.lib.notmuch_database_create_with_config(cls._db_path_encode(path),
cls._cfg_path_encode(config),
capi.ffi.NULL,
db_pp, cmsg)
if cmsg[0]:
msg = capi.ffi.string(cmsg[0]).decode(errors='replace')
@ -176,7 +207,7 @@ class Database(base.NotmuchObject):
ret = capi.lib.notmuch_database_destroy(db_pp[0])
if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
raise errors.NotmuchError(ret)
return cls(path, cls.MODE.READ_WRITE)
return cls(path, cls.MODE.READ_WRITE, config=config)
@staticmethod
def default_path(cfg_path=None):
@ -187,8 +218,8 @@ class Database(base.NotmuchObject):
:param cfg_path: The pathname of the notmuch configuration file.
If not specified tries to use the pathname provided in the
:env:`NOTMUCH_CONFIG` environment variable and falls back
to :file:`~/.notmuch-config.
:envvar:`NOTMUCH_CONFIG` environment variable and falls back
to :file:`~/.notmuch-config`.
:type cfg_path: str, bytes, os.PathLike or pathlib.Path.
:returns: The path of the database, which does not necessarily
@ -198,8 +229,11 @@ class Database(base.NotmuchObject):
be opened.
:raises configparser.Error: or subclasses if the configuration
file can not be parsed.
:raises NotmuchError if the config file does not have the
:raises NotmuchError: if the config file does not have the
database.path setting.
.. deprecated:: 0.35
Use the ``config`` parameter to :meth:`__init__` or :meth:`__create__` instead.
"""
if not cfg_path:
cfg_path = _config_pathname()

View file

@ -4,7 +4,7 @@ dir := doc
# You can set these variables from the command line.
SPHINXOPTS := -q
SPHINXBUILD = sphinx-build
SPHINXBUILD = env LD_LIBRARY_PATH=${NOTMUCH_BUILDDIR}/lib sphinx-build
DOCBUILDDIR := $(dir)/_build
# Internal variables.
@ -35,7 +35,7 @@ endif
INFO_INFO_FILES := $(INFO_TEXI_FILES:.texi=.info)
.PHONY: sphinx-html sphinx-texinfo sphinx-info
.PHONY: sphinx-html sphinx-texinfo sphinx-info doc-prereqs
.PHONY: install-man build-man apidocs install-apidocs
@ -46,15 +46,19 @@ ifeq ($(WITH_EMACS),1)
$(DOCBUILDDIR)/.roff.stamp $(DOCBUILDDIR)/.html.stamp $(DOCBUILDDIR)/.texi.stamp : docstring.stamp
endif
ifeq ($(HAVE_PYTHON3_CFFI),1)
doc-prereqs: python-cffi-bindings
endif
sphinx-html: $(DOCBUILDDIR)/.html.stamp
$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES)
$(DOCBUILDDIR)/.html.stamp: $(ALL_RST_FILES) doc-prereqs
$(SPHINXBUILD) -b html -d $(DOCBUILDDIR)/html_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/html
touch $@
sphinx-texinfo: $(DOCBUILDDIR)/.texi.stamp
$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES)
$(DOCBUILDDIR)/.texi.stamp: $(ALL_RST_FILES) doc-prereqs
$(SPHINXBUILD) -b texinfo -d $(DOCBUILDDIR)/texinfo_doctrees $(ALLSPHINXOPTS) $(DOCBUILDDIR)/texinfo
touch $@

View file

@ -25,7 +25,7 @@ for pathdir in ['.', '..']:
version=infile.read().replace('\n','')
# for autodoc
sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'notmuch2'))
sys.path.insert(0, os.path.join(location, '..', 'bindings', 'python-cffi', 'build', 'stage'))
# read generated config
for pathdir in ['.', '..']:

View file

@ -259,6 +259,8 @@ squery.<name>
FILES
=====
.. _config_search:
CONFIGURATION
-------------

View file

@ -259,7 +259,15 @@ _notmuch_config_load_from_database (notmuch_database_t *notmuch)
for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
const char *key = notmuch_config_list_key (list);
char *normalized_val = _expand_path (list, key, notmuch_config_list_value (list));
char *normalized_val = NULL;
/* If we opened from a given path, do not overwrite it */
if (strcmp (key, "database.path") == 0 &&
(notmuch->params & NOTMUCH_PARAM_DATABASE) &&
notmuch->xapian_db)
continue;
normalized_val = _expand_path (list, key, notmuch_config_list_value (list));
_notmuch_string_map_append (notmuch->config, key, normalized_val);
talloc_free (normalized_val);
}
@ -432,6 +440,13 @@ _notmuch_config_load_from_file (notmuch_database_t *notmuch,
status = NOTMUCH_STATUS_FILE_ERROR;
goto DONE;
}
/* If we opened from a given path, do not overwrite it */
if (strcmp (absolute_key, "database.path") == 0 &&
(notmuch->params & NOTMUCH_PARAM_DATABASE) &&
notmuch->xapian_db)
continue;
normalized_val = _expand_path (notmuch, absolute_key, val);
_notmuch_string_map_set (notmuch->config, absolute_key, normalized_val);
g_free (val);

View file

@ -190,6 +190,39 @@ operator& (notmuch_field_flag_t a, notmuch_field_flag_t b)
Xapian::QueryParser::FLAG_WILDCARD | \
Xapian::QueryParser::FLAG_PURE_NOT)
/*
* Which parameters were explicit when the database was opened */
typedef enum {
NOTMUCH_PARAM_NONE = 0,
NOTMUCH_PARAM_DATABASE = 1 << 0,
NOTMUCH_PARAM_CONFIG = 1 << 1,
NOTMUCH_PARAM_PROFILE = 1 << 2,
} notmuch_open_param_t;
/*
* define bitwise operators to hide casts */
inline notmuch_open_param_t
operator| (notmuch_open_param_t a, notmuch_open_param_t b)
{
return static_cast<notmuch_open_param_t>(
static_cast<unsigned>(a) | static_cast<unsigned>(b));
}
inline notmuch_open_param_t&
operator|= (notmuch_open_param_t &a, notmuch_open_param_t b)
{
a = a | b;
return a;
}
inline notmuch_open_param_t
operator& (notmuch_open_param_t a, notmuch_open_param_t b)
{
return static_cast<notmuch_open_param_t>(
static_cast<unsigned>(a) & static_cast<unsigned>(b));
}
struct _notmuch_database {
bool exception_reported;
@ -249,6 +282,9 @@ struct _notmuch_database {
/* Cached and possibly overridden configuration */
notmuch_string_map_t *config;
/* Track what parameters were specified when opening */
notmuch_open_param_t params;
};
/* Prior to database version 3, features were implied by the database

View file

@ -246,7 +246,7 @@ _choose_database_path (void *ctx,
}
static notmuch_database_t *
_alloc_notmuch ()
_alloc_notmuch (const char *database_path, const char *config_path, const char *profile)
{
notmuch_database_t *notmuch;
@ -262,6 +262,15 @@ _alloc_notmuch ()
notmuch->transaction_count = 0;
notmuch->transaction_threshold = 0;
notmuch->view = 1;
notmuch->params = NOTMUCH_PARAM_NONE;
if (database_path)
notmuch->params |= NOTMUCH_PARAM_DATABASE;
if (config_path)
notmuch->params |= NOTMUCH_PARAM_CONFIG;
if (profile)
notmuch->params |= NOTMUCH_PARAM_PROFILE;
return notmuch;
}
@ -509,7 +518,7 @@ notmuch_database_open_with_config (const char *database_path,
_notmuch_init ();
notmuch = _alloc_notmuch ();
notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
@ -609,7 +618,7 @@ notmuch_database_create_with_config (const char *database_path,
_notmuch_init ();
notmuch = _alloc_notmuch ();
notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
@ -811,7 +820,7 @@ notmuch_database_load_config (const char *database_path,
_notmuch_init ();
notmuch = _alloc_notmuch ();
notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;

View file

@ -306,7 +306,23 @@ EOF
output2=$(notmuch --config='' config get ${key})
notmuch config set ${key}
test_expect_equal "${output}+${output2}" "${value}+"
;;
;&
split)
test_begin_subtest "'to' header does not crash (python-cffi) ($config)"
echo 'notmuch@notmuchmail.org' > EXPECTED
test_python <<EOF
from notmuch2 import Database
db=Database(config=Database.CONFIG.SEARCH)
m=db.find('20091117232137.GA7669@griffis1.net')
to=m.header('To')
print(to)
EOF
test_expect_equal_file EXPECTED OUTPUT
;& # fall through
esac
case $config in
split|XDG*)
esac
restore_config
rm -rf home/.local

View file

@ -7,8 +7,14 @@ if [ $NOTMUCH_HAVE_PYTHON3_CFFI -eq 0 -o $NOTMUCH_HAVE_PYTHON3_PYTEST -eq 0 ]; t
fi
test_begin_subtest "python cffi tests"
test_begin_subtest "python cffi tests (NOTMUCH_CONFIG set)"
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
test_begin_subtest "python cffi tests (NOTMUCH_CONFIG unset)"
pytest_dir=$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage
printf "[pytest]\nminversion = 3.0\naddopts = -ra\n" > $pytest_dir/pytest.ini
unset NOTMUCH_CONFIG
test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
test_done

View file

@ -876,13 +876,13 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "open: database set to null on missing config (env)"
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
NOTMUCH_CONFIG="/nonexistent"
export NOTMUCH_CONFIG="/nonexistent"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
notmuch_status_t st = notmuch_database_open_with_config(argv[1],
NOTMUCH_DATABASE_MODE_READ_ONLY,
NULL, NULL, &db, NULL);
EOF
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 1
@ -903,12 +903,12 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "create: database set to null on missing config (env)"
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
NOTMUCH_CONFIG="/nonexistent"
export NOTMUCH_CONFIG="/nonexistent"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
notmuch_status_t st = notmuch_database_create_with_config(argv[1],
NULL, NULL, &db, NULL);
EOF
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 1
@ -929,11 +929,11 @@ test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "load_config: database non-null on missing config (env)"
old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
NOTMUCH_CONFIG="/nonexistent"
export NOTMUCH_CONFIG="/nonexistent"
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
notmuch_status_t st = notmuch_database_load_config(argv[1], NULL, NULL, &db, NULL);
EOF
NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
cat <<EOF> EXPECTED
== stdout ==
db == NULL: 0
@ -952,4 +952,25 @@ db == NULL: 1
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "open: database parameter overrides implicit config"
notmuch config set database.path ${MAIL_DIR}/nonexistent
cat c_head3 - c_tail3 <<'EOF' | test_C ${MAIL_DIR}
const char *path = NULL;
notmuch_status_t st = notmuch_database_open_with_config(argv[1],
NOTMUCH_DATABASE_MODE_READ_ONLY,
NULL, NULL, &db, NULL);
printf ("status: %d\n", st);
path = notmuch_database_get_path (db);
printf ("path: %s\n", path ? path : "(null)");
EOF
cat <<EOF> EXPECTED
== stdout ==
status: 0
path: MAIL_DIR
db == NULL: 0
== stderr ==
EOF
notmuch_dir_sanitize < OUTPUT > OUTPUT.clean
test_expect_equal_file EXPECTED OUTPUT.clean
test_done

View file

@ -909,7 +909,7 @@ test_done () {
test_python () {
# Note: if there is need to print debug information from python program,
# use stdout = os.fdopen(6, 'w') or stderr = os.fdopen(7, 'w')
PYTHONPATH="$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
PYTHONPATH="$NOTMUCH_BUILDDIR/bindings/python-cffi/build/stage:$NOTMUCH_SRCDIR/bindings/python${PYTHONPATH:+:$PYTHONPATH}" \
$NOTMUCH_PYTHON -B - > OUTPUT
}