notmuch_status_to_string (notmuch_status_t status);
notmuch_status_t
- notmuch_database_create_verbose (const char *path,
- 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_mode_t mode,
- 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_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_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_close (notmuch_database_t *database);
notmuch_status_t
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
: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.
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
: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.
MODE = Mode
SORT = QuerySortOrder
EXCLUDE = QueryExclude
+ CONFIG = ConfigFile
AddedMessage = collections.namedtuple('AddedMessage', ['msg', 'dup'])
_db_p = base.MemoryPointer()
STR_MODE_MAP = {
'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])
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
: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),
- db_pp, cmsg)
+ 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')
capi.lib.free(cmsg[0])
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):
: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
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()
# 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.
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
$(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 $@
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 ['.', '..']:
FILES
=====
+.. _config_search:
+
CONFIGURATION
-------------
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);
}
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);
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;
/* 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
}
static notmuch_database_t *
-_alloc_notmuch ()
+_alloc_notmuch (const char *database_path, const char *config_path, const char *profile)
{
notmuch_database_t *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;
}
_notmuch_init ();
- notmuch = _alloc_notmuch ();
+ notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
_notmuch_init ();
- notmuch = _alloc_notmuch ();
+ notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
_notmuch_init ();
- notmuch = _alloc_notmuch ();
+ notmuch = _alloc_notmuch (database_path, config_path, profile);
if (! notmuch) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
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
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
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
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
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
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
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
}