+Notmuch 0.32.3 (2021-08-17)
+===========================
+
+Library
+-------
+
+Restore location of database via `MAILDIR` environment variable, which
+was broken in 0.32.
+
+Bump libnotmuch minor version to match the documentation in
+`notmuch.h`.
+
+Correct documentation for deprecated database opening functions to
+point out that they (still) do not load configuration information.
+
+CLI
+---
+
+Restore "notmuch config get built_with.*", which was broken in 0.32.
+
+Notmuch 0.32.2 (2021-06-27)
+===========================
+
+General
+-------
+
+Fix a bug from 2017 that can add duplicate thread-id terms to message
+documents.
+
+CLI
+---
+
+Fix small memory leak in notmuch new.
+
+Emacs
+-----
+
+Add `(require 'seq)` for `seq-some`.
+
+Documentation
+-------------
+
+Fix man page build for Sphinx 4.x. Fix variable name in emacs docs.
+
+Tests
+-----
+
+Fix backup creation in `perf-test/T00-new`. Check openssl
+prerequisite in `add_gpgsm_home`.
+
+Notmuch 0.32.1 (2021-05-15)
+===========================
+
+General
+-------
+
+Restore handling of relative values for `database.path` that was
+broken by 0.32. Extend this handling to `database.mail_root`,
+`database.backup_dir`, and `database.hook_dir`.
+
+Reload certain metadata from Xapian database in
+notmuch_database_reopen. This fixes a bug when adding messages to the
+database in a pre-new hook.
+
+Fix default of `$HOME/mail` for `database.path`. In release 0.32, this
+default worked only in "notmuch config".
+
+Emacs
+-----
+
+Restore the dynamically bound variables `tag-changes` and `query` in
+in `notmuch-before-tag-hook` and `notmuch-after-tag-hook`.
+
+Notmuch 0.32 (2021-05-02)
+=========================
+
+General
+-------
+
+This release includes a significant overhaul of the configuration
+management facilities for notmuch. The previous distinction between
+configuration items that can be modified via plain text configuration
+files and those that must be set in the database via the "notmuch
+config" subcommand is gone, and all configuration items can be set in
+both ways. The external configuration file overrides configuration
+items in the database. The location of database, hooks, and
+configuration files is now more flexible, with several new
+configuration variables. In particular XDG locations are now supported
+as fallbacks for database, configuration and hooks. For more
+information see `notmuch-config(1)`.
+
+Library
+-------
+
+To support the new configuration facilities, several functions and
+constants have been added to the notmuch API. Most notably:
+
+- `notmuch_database_create_with_config`
+- `notmuch_database_open_with_config`
+- `notmuch_database_load_config`
+- `notmuch_config_get`
+
+A previously requested API change is that `notmuch_database_reopen` is
+now exposed (and generalized).
+
+The previously severe slowdowns from large numbers calls to
+notmuch_database_remove_message or notmuch_message_delete in one
+session has been fixed.
+
+As always, the canonical source of API documentation is
+`lib/notmuch.h`, or the doxygen formatted documentation in `notmuch(3)`
+
+CLI
+---
+
+The `notmuch config set` subcommand gained a `--database` argument to
+specify that the database should be updated, rather than a config file.
+
+The speed of `notmuch new` and `notmuch reindex` in dealing with large
+numbers of mail file deletions is significantly improved.
+
+Emacs
+-----
+
+Completion related updates include: de-duplicating tags offered for
+completion, use the actual initial input in address completion, allow
+users to opt out of notmuch address completion, and do not force Ido
+when prompting for senders.
+
+Some keymaps used to contain bindings for unnamed commands. These
+lambda expressions have been replaced by named commands (symbols), to
+ease customization.
+
+Lexical binding is now used in all notmuch-emacs libraries.
+
+Fix bug in calling `notmuch-mua-mail` with a non-nil RETURN-ACTION.
+
+Removed, inlined or renamed functions and variables:
+ `notmuch-address-locate-command`,
+ `notmuch-documentation-first-line`, `notmuch-folder`,
+ `notmuch-hello-trim', `notmuch-hello-versions` => `notmuch-version`,
+ `notmuch-remove-if-not`, `notmuch-search-disjunctive-regexp`,
+ `notmuch-sexp-eof`, `notmuch-split-content-type`, and
+ `notmuch-tree-button-activate`.
+
+Keymaps are no longer fset, which means they need to be referred to in
+define-key directly (without quotes). If your Emacs configuration has a
+keybinding like:
+ (define-key 'notmuch-show-mode-map "7" 'foo)
+you should change it to:
+ (define-key notmuch-show-mode-map "7" 'foo)
+
Notmuch 0.31.4 (2021-02-18)
===========================
NOTMUCH_STATUS_UPGRADE_REQUIRED,
NOTMUCH_STATUS_PATH_ERROR,
NOTMUCH_STATUS_ILLEGAL_ARGUMENT,
+ NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL,
+ NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION,
+ NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL,
+ NOTMUCH_STATUS_NO_CONFIG,
+ NOTMUCH_STATUS_NO_DATABASE,
+ NOTMUCH_STATUS_DATABASE_EXISTS,
NOTMUCH_STATUS_LAST_STATUS
} notmuch_status_t;
typedef enum {
PathError,
capi.lib.NOTMUCH_STATUS_ILLEGAL_ARGUMENT:
IllegalArgumentError,
+ capi.lib.NOTMUCH_STATUS_NO_CONFIG:
+ NoConfigError,
+ capi.lib.NOTMUCH_STATUS_NO_DATABASE:
+ NoDatabaseError,
+ capi.lib.NOTMUCH_STATUS_DATABASE_EXISTS:
+ DatabaseExistsError,
}
return types[status]
class UpgradeRequiredError(NotmuchError): pass
class PathError(NotmuchError): pass
class IllegalArgumentError(NotmuchError): pass
-
+class NoConfigError(NotmuchError): pass
+class NoDatabaseError(NotmuchError): pass
+class DatabaseExistsError(NotmuchError): pass
class ObjectDestroyedError(NotmuchError):
"""The object has already been destroyed and it's memory freed.
db.create(tmppath)
def test_create_existing(self, tmppath, db):
- with pytest.raises(errors.FileError):
+ with pytest.raises(errors.DatabaseExistsError):
dbmod.Database.create(path=tmppath)
def test_close(self, db):
# this file should be kept in sync with ../../../version
-__VERSION__ = '0.31.4'
+__VERSION__ = '0.32.3'
SOVERSION = '5'
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""
This file is part of notmuch.
*
* Notmuch database interaction
*/
- notmuch_rb_cDatabase = rb_define_class_under (mod, "Database", rb_cData);
+ notmuch_rb_cDatabase = rb_define_class_under (mod, "Database", rb_cObject);
rb_define_alloc_func (notmuch_rb_cDatabase, notmuch_rb_database_alloc);
rb_define_singleton_method (notmuch_rb_cDatabase, "open", notmuch_rb_database_open, -1); /* in database.c */
rb_define_method (notmuch_rb_cDatabase, "initialize", notmuch_rb_database_initialize, -1); /* in database.c */
*
* Notmuch directory
*/
- notmuch_rb_cDirectory = rb_define_class_under (mod, "Directory", rb_cData);
+ notmuch_rb_cDirectory = rb_define_class_under (mod, "Directory", rb_cObject);
rb_undef_method (notmuch_rb_cDirectory, "initialize");
rb_define_method (notmuch_rb_cDirectory, "destroy!", notmuch_rb_directory_destroy, 0); /* in directory.c */
rb_define_method (notmuch_rb_cDirectory, "mtime", notmuch_rb_directory_get_mtime, 0); /* in directory.c */
*
* Notmuch file names
*/
- notmuch_rb_cFileNames = rb_define_class_under (mod, "FileNames", rb_cData);
+ notmuch_rb_cFileNames = rb_define_class_under (mod, "FileNames", rb_cObject);
rb_undef_method (notmuch_rb_cFileNames, "initialize");
rb_define_method (notmuch_rb_cFileNames, "destroy!", notmuch_rb_filenames_destroy, 0); /* in filenames.c */
rb_define_method (notmuch_rb_cFileNames, "each", notmuch_rb_filenames_each, 0); /* in filenames.c */
*
* Notmuch query
*/
- notmuch_rb_cQuery = rb_define_class_under (mod, "Query", rb_cData);
+ notmuch_rb_cQuery = rb_define_class_under (mod, "Query", rb_cObject);
rb_undef_method (notmuch_rb_cQuery, "initialize");
rb_define_method (notmuch_rb_cQuery, "destroy!", notmuch_rb_query_destroy, 0); /* in query.c */
rb_define_method (notmuch_rb_cQuery, "sort", notmuch_rb_query_get_sort, 0); /* in query.c */
*
* Notmuch threads
*/
- notmuch_rb_cThreads = rb_define_class_under (mod, "Threads", rb_cData);
+ notmuch_rb_cThreads = rb_define_class_under (mod, "Threads", rb_cObject);
rb_undef_method (notmuch_rb_cThreads, "initialize");
rb_define_method (notmuch_rb_cThreads, "destroy!", notmuch_rb_threads_destroy, 0); /* in threads.c */
rb_define_method (notmuch_rb_cThreads, "each", notmuch_rb_threads_each, 0); /* in threads.c */
*
* Notmuch messages
*/
- notmuch_rb_cMessages = rb_define_class_under (mod, "Messages", rb_cData);
+ notmuch_rb_cMessages = rb_define_class_under (mod, "Messages", rb_cObject);
rb_undef_method (notmuch_rb_cMessages, "initialize");
rb_define_method (notmuch_rb_cMessages, "destroy!", notmuch_rb_messages_destroy, 0); /* in messages.c */
rb_define_method (notmuch_rb_cMessages, "each", notmuch_rb_messages_each, 0); /* in messages.c */
*
* Notmuch thread
*/
- notmuch_rb_cThread = rb_define_class_under (mod, "Thread", rb_cData);
+ notmuch_rb_cThread = rb_define_class_under (mod, "Thread", rb_cObject);
rb_undef_method (notmuch_rb_cThread, "initialize");
rb_define_method (notmuch_rb_cThread, "destroy!", notmuch_rb_thread_destroy, 0); /* in thread.c */
rb_define_method (notmuch_rb_cThread, "thread_id", notmuch_rb_thread_get_thread_id, 0); /* in thread.c */
*
* Notmuch message
*/
- notmuch_rb_cMessage = rb_define_class_under (mod, "Message", rb_cData);
+ notmuch_rb_cMessage = rb_define_class_under (mod, "Message", rb_cObject);
rb_undef_method (notmuch_rb_cMessage, "initialize");
rb_define_method (notmuch_rb_cMessage, "destroy!", notmuch_rb_message_destroy, 0); /* in message.c */
rb_define_method (notmuch_rb_cMessage, "message_id", notmuch_rb_message_get_message_id, 0); /* in message.c */
*
* Notmuch tags
*/
- notmuch_rb_cTags = rb_define_class_under (mod, "Tags", rb_cData);
+ notmuch_rb_cTags = rb_define_class_under (mod, "Tags", rb_cObject);
rb_undef_method (notmuch_rb_cTags, "initialize");
rb_define_method (notmuch_rb_cTags, "destroy!", notmuch_rb_tags_destroy, 0); /* in tags.c */
rb_define_method (notmuch_rb_cTags, "each", notmuch_rb_tags_each, 0); /* in tags.c */
continue;
*arg_desc->opt_keyword = keywords->value;
- fprintf (stderr, "Warning: No known keyword option given for \"%s\", choosing value \"%s\"."
- " Please specify the argument explicitly!\n", arg_desc->name, arg_desc->keyword_no_arg_value);
+ fprintf (stderr,
+ "Warning: No known keyword option given for \"%s\", choosing value \"%s\"."
+ " Please specify the argument explicitly!\n", arg_desc->name,
+ arg_desc->keyword_no_arg_value);
return OPT_GIVEBACK;
}
- fprintf (stderr, "No matching keyword for option \"%s\" and default value \"%s\" is invalid.\n", arg_str, arg_desc->name);
+ fprintf (stderr,
+ "No matching keyword for option \"%s\" and default value \"%s\" is invalid.\n",
+ arg_str,
+ arg_desc->name);
return OPT_FAILED;
}
if (next != '\0')
- fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str, arg_desc->name);
+ fprintf (stderr, "Unknown keyword argument \"%s\" for option \"%s\".\n", arg_str,
+ arg_desc->name);
else
fprintf (stderr, "Option \"%s\" needs a keyword argument.\n", arg_desc->name);
return OPT_FAILED;
} else if (strcmp (arg_str, "false") == 0) {
value = false;
} else {
- fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
+ fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str,
+ arg_desc->name);
return OPT_FAILED;
}
const notmuch_opt_desc_t *try;
const char *next_arg = NULL;
+
if (opt_index < argc - 1 && strncmp (argv[opt_index + 1], "--", 2) != 0)
next_arg = argv[opt_index + 1];
notmuch_compat_srcs :=
-ifneq ($(HAVE_CANONICALIZE_FILE_NAME),1)
-notmuch_compat_srcs += $(dir)/canonicalize_file_name.c
-endif
-
ifneq ($(HAVE_GETLINE),1)
notmuch_compat_srcs += $(dir)/getline.c $(dir)/getdelim.c
endif
+++ /dev/null
-#include "compat.h"
-#include <limits.h>
-#undef _GNU_SOURCE
-#include <stdlib.h>
-
-char *
-canonicalize_file_name (const char *path)
-{
-#ifdef PATH_MAX
- char *resolved_path = malloc (PATH_MAX + 1);
- if (resolved_path == NULL)
- return NULL;
-
- return realpath (path, resolved_path);
-#else
-#error undefined PATH_MAX _and_ missing canonicalize_file_name not supported
-#endif
-}
#define _POSIX_PTHREAD_SEMANTICS 1
#endif
-#if ! HAVE_CANONICALIZE_FILE_NAME
-/* we only call this function from C, and this makes testing easier */
-#ifndef __cplusplus
-char *
-canonicalize_file_name (const char *path);
-#endif
-#endif
-
#if ! HAVE_GETLINE
#include <stdio.h>
#include <unistd.h>
+notmuch (0.32.3-1) unstable; urgency=medium
+
+ * new upstream bugfix release
+ * fixes for a few configuration related bugs introduced in 0.32
+ * bump libnotmuch minor version to match documentation.
+
+ -- David Bremner <bremner@debian.org> Tue, 17 Aug 2021 17:16:09 -0700
+
+notmuch (0.32.2-1) experimental; urgency=medium
+
+ * New upstream bugfix release
+ * Fix for memory leak in "notmuch new" introduced in 0.32
+ * Fix for bug from 2017 that can add duplicate thread-ids to messages.
+
+ -- David Bremner <bremner@debian.org> Sat, 26 Jun 2021 22:33:56 -0300
+
+notmuch (0.32.1-1) experimental; urgency=medium
+
+ * New upstream bugfix release
+ * Configuration bug fixes (see /usr/share/doc/notmuch/NEWS.gz)
+ * Bug fix for {pre,after}-tag hooks in emacs, related to lexical scope
+ transition.
+
+ -- David Bremner <bremner@debian.org> Sat, 15 May 2021 09:01:27 -0300
+
+notmuch (0.32-1) experimental; urgency=medium
+
+ * New upstream release
+ * Speedup for handling deleted message files
+ * New configuration features (see /usr/share/doc/notmuch/NEWS.gz)
+ * Emacs interface codebase cleanup
+
+ -- David Bremner <bremner@debian.org> Sun, 02 May 2021 07:05:15 -0300
+
+notmuch (0.32~rc2-1) experimental; urgency=medium
+
+ * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Wed, 28 Apr 2021 07:05:22 -0300
+
+notmuch (0.32~rc1-1) experimental; urgency=medium
+
+ * New upstream release candidate
+
+ -- David Bremner <bremner@debian.org> Sat, 24 Apr 2021 12:46:10 -0300
+
notmuch (0.31.4-2) unstable; urgency=medium
* Cherry pick upstream commit 3f4de98e7c8, which fixes a bug where
libnotmuch.so.5 libnotmuch5 #MINVER#
* Build-Depends-Package: libnotmuch-dev
notmuch_built_with@Base 0.23~rc0
+ notmuch_config_get@Base 0.32~rc0
+ notmuch_config_get_bool@Base 0.32~rc0
+ notmuch_config_get_pairs@Base 0.32~rc0
+ notmuch_config_get_values@Base 0.32~rc0
+ notmuch_config_get_values_string@Base 0.32~rc0
notmuch_config_list_destroy@Base 0.23~rc0
notmuch_config_list_key@Base 0.23~rc0
notmuch_config_list_move_to_next@Base 0.23~rc0
notmuch_config_list_valid@Base 0.23~rc0
notmuch_config_list_value@Base 0.23~rc0
+ notmuch_config_pairs_destroy@Base 0.32~rc0
+ notmuch_config_pairs_key@Base 0.32~rc0
+ notmuch_config_pairs_move_to_next@Base 0.32~rc0
+ notmuch_config_pairs_valid@Base 0.32~rc0
+ notmuch_config_pairs_value@Base 0.32~rc0
+ notmuch_config_path@Base 0.32~rc0
+ notmuch_config_set@Base 0.32~rc0
+ notmuch_config_values_destroy@Base 0.32~rc0
+ notmuch_config_values_get@Base 0.32~rc0
+ notmuch_config_values_move_to_next@Base 0.32~rc0
+ notmuch_config_values_start@Base 0.32~rc0
+ notmuch_config_values_valid@Base 0.32~rc0
notmuch_database_add_message@Base 0.3
notmuch_database_begin_atomic@Base 0.9~rc1
notmuch_database_close@Base 0.13~rc1
notmuch_database_compact@Base 0.17~rc1
+ notmuch_database_compact_db@Base 0.32~rc0
notmuch_database_create@Base 0.3
notmuch_database_create_verbose@Base 0.20~rc1
+ notmuch_database_create_with_config@Base 0.32~rc0
notmuch_database_destroy@Base 0.13~rc1
notmuch_database_end_atomic@Base 0.9~rc1
notmuch_database_find_message@Base 0.9~rc2
notmuch_database_get_revision@Base 0.21~rc1
notmuch_database_get_version@Base 0.3
notmuch_database_index_file@Base 0.26~rc0
+ notmuch_database_load_config@Base 0.32~rc0
notmuch_database_needs_upgrade@Base 0.3
notmuch_database_open@Base 0.3
notmuch_database_open_verbose@Base 0.20~rc1
+ notmuch_database_open_with_config@Base 0.32~rc0
notmuch_database_remove_message@Base 0.3
+ notmuch_database_reopen@Base 0.32~rc0
notmuch_database_set_config@Base 0.23~rc0
notmuch_database_status_string@Base 0.20~rc1
notmuch_database_upgrade@Base 0.3
(c++)"typeinfo for Xapian::Error@Base" 0.6.1
(c++)"typeinfo for Xapian::DatabaseError@Base" 0.24~rc0
(c++)"typeinfo for Xapian::DatabaseModifiedError@Base" 0.24~rc0
+ (c++)"typeinfo for Xapian::DatabaseOpeningError@Base" 0.32~rc0
(c++|optional=present with Xapian 1.4)"typeinfo for Xapian::QueryParserError@Base" 0.23~rc0
(c++)"typeinfo name for Xapian::LogicError@Base" 0.6.1
(c++)"typeinfo name for Xapian::RuntimeError@Base" 0.6.1
(c++)"typeinfo name for Xapian::Error@Base" 0.6.1
(c++)"typeinfo name for Xapian::DatabaseError@Base" 0.24~rc0
(c++)"typeinfo name for Xapian::DatabaseModifiedError@Base" 0.24~rc0
+ (c++)"typeinfo name for Xapian::DatabaseOpeningError@Base" 0.32~rc0
(c++|optional=present with Xapian 1.4)"typeinfo name for Xapian::QueryParserError@Base" 0.23~rc0
border-bottom-right-radius: {border_radius};
}}
tbody:nth-child(4n+1) tr td {{
+ color: #000;
background-color: #ffd96e;
}}
tbody:nth-child(4n+3) tr td {{
+ color: #000;
background-color: #bce;
}}
hr {{
# indent_brace = 0
indent_class = true
+
+# line width / line splitting
+code_width 102
+ls_for_split_full True
+ls_func_split_full True
+ls_code_width True
notmuch_authors = u'Carl Worth and many others'
+man_make_section_directory = False
+
man_pages = [
('man1/notmuch', 'notmuch',
u'thread-based email index, search, and tagging',
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 8
ALIASES =
-TCL_SUBST =
OPTIMIZE_OUTPUT_FOR_C = YES
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
**notmuch** **config** **get** <*section*>.<*item*>
-**notmuch** **config** **set** <*section*>.<*item*> [*value* ...]
+**notmuch** **config** **set** [--database] <*section*>.<*item*> [*value* ...]
**notmuch** **config** **list**
The **config** command can be used to get or set settings in the notmuch
configuration file and corresponding database.
-Items marked **[STORED IN DATABASE]** are only in the database. They
-should not be placed in the configuration file, and should be accessed
-programmatically as described in the SYNOPSIS above.
-
**get**
The value of the specified configuration item is printed to
stdout. If the item has multiple values (it is a list), each value
If no values are provided, the specified configuration item will
be removed from the configuration file.
+ With the `--database` option, updates configuration metadata
+ stored in the database, rather than the default (text)
+ configuration file.
+
**list**
Every configuration item is printed to stdout, each on a separate
line of the form::
characters. In a multiple-value item (a list), the values are
separated by semicolon characters.
-The available configuration items are described below.
+The available configuration items are described below. Non-absolute
+paths are presumed relative to `$HOME` for items in section
+**database**.
**database.path**
+ Notmuch will store its database here, (in
+ sub-directory named ``.notmuch`` if **database.mail\_root**
+ is unset).
+
+ Default: ``$MAILDIR`` variable if set, otherwise ``$HOME/mail``.
+
+**database.mail_root**
The top-level directory where your mail currently exists and to
where mail will be delivered in the future. Files should be
- individual email messages. Notmuch will store its database within
- a sub-directory of the path configured here named ``.notmuch``.
+ individual email messages.
- Default: ``$MAILDIR`` variable if set, otherwise ``$HOME/mail``.
+ History: this configuration value was introduced in notmuch 0.32.
+
+ Default: For compatibility with older configurations, the value of
+ database.path is used if **database.mail\_root** is unset.
+
+**database.backup_dir**
+ Directory to store tag dumps when upgrading database.
+
+ History: this configuration value was introduced in notmuch 0.32.
+
+ Default: A sibling directory of the Xapian database called
+ `backups`.
+
+**database.hook_dir**
+ Directory containing hooks run by notmuch commands. See
+ **notmuch-hooks(5)**.
+
+ History: this configuration value was introduced in notmuch 0.32.
+
+ Default: See HOOKS, below.
**user.name**
Your full name.
Default: ``true``.
-**index.decrypt** **[STORED IN DATABASE]**
+**index.decrypt**
Policy for decrypting encrypted messages during indexing. Must be
one of: ``false``, ``auto``, ``nostash``, or ``true``.
Default: ``auto``.
-**index.header.<prefix>** **[STORED IN DATABASE]**
+**index.header.<prefix>**
Define the query prefix <prefix>, based on a mail header. For
example ``index.header.List=List-Id`` will add a probabilistic
prefix ``List:`` that searches the ``List-Id`` field. User
(since notmuch 0.30, "compact" and "field_processor" are
always included.)
-**query.<name>** **[STORED IN DATABASE]**
+**query.<name>**
Expansion for named query called <name>. See
**notmuch-search-terms(7)** for more information about named
queries.
of notmuch.
**NOTMUCH\_CONFIG**
- Specifies the location of the notmuch configuration file. Notmuch
- will use ${HOME}/.notmuch-config if this variable is not set.
+ Specifies the location of the notmuch configuration file.
+
+**NOTMUCH_PROFILE**
+ Selects among notmuch configurations.
+
+FILES
+=====
+
+CONFIGURATION
+-------------
+
+If ``NOTMUCH_CONFIG`` is unset, notmuch tries (in order)
+
+- ``$XDG_CONFIG_HOME/notmuch/<profile>/config`` where ``<profile>`` is
+ defined by ``$NOTMUCH_PROFILE`` or "default"
+- ``${HOME}/.notmuch-config<profile>`` where ``<profile>`` is
+ ``.$NOTMUCH_PROFILE`` or ""
+
+HOOKS
+-----
+
+If ``database.hook_dir`` is unset, notmuch tries (in order)
+
+- ``$XDG_CONFIG_HOME/notmuch/<profile>/hooks`` where ``<profile>`` is
+ defined by ``$NOTMUCH_PROFILE`` or "default"
+- ``<database.path>/.notmuch/hooks``
SEE ALSO
========
``--config=FILE``
Specify the configuration file to use. This overrides any
- configuration file specified by ${NOTMUCH\_CONFIG}.
+ configuration file specified by ${NOTMUCH\_CONFIG}. The empty
+ string is a permitted and sometimes useful value of *FILE*, which
+ tells ``notmuch`` to use only configuration metadata from the database.
``--uuid=HEX``
Enforce that the database UUID (a unique identifier which persists
SYNOPSIS
========
-$DATABASEDIR/.notmuch/hooks/*
+<hook_dir>/{pre-new, post-new, post-insert}
DESCRIPTION
===========
Hooks are scripts (or arbitrary executables or symlinks to such) that
notmuch invokes before and after certain actions. These scripts reside
-in the .notmuch/hooks directory within the database directory and must
-have executable permissions.
+in a directory defined as described in **notmuch-config(1)**. They
+must have executable permissions.
The currently available hooks are described below.
The list of saved searches, including names, queries, and
additional per-query options.
-:index:`notmuch-saved-searches-sort-function`
+:index:`notmuch-saved-search-sort-function`
This variable controls how saved searches should be sorted. A value
of ``nil`` displays the saved searches in the order they are stored
in ‘notmuch-saved-searches’.
-;;; coolj.el --- automatically wrap long lines -*- coding:utf-8 -*-
+;;; coolj.el --- automatically wrap long lines -*- lexical-binding: t; coding: utf-8 -*-
;; Copyright (C) 2000, 2001, 2004-2009 Free Software Foundation, Inc.
;;; Commentary:
-;;; This is a simple derivative of some functionality from
-;;; `longlines.el'. The key difference is that this version will
-;;; insert a prefix at the head of each wrapped line. The prefix is
-;;; calculated from the originating long line.
+;; This is a simple derivative of some functionality from
+;; `longlines.el'. The key difference is that this version will
+;; insert a prefix at the head of each wrapped line. The prefix is
+;; calculated from the originating long line.
-;;; No minor-mode is provided, the caller is expected to call
-;;; `coolj-wrap-region' to wrap the region of interest.
+;; No minor-mode is provided, the caller is expected to call
+;; `coolj-wrap-region' to wrap the region of interest.
;;; Code:
:group 'coolj
:type 'regexp)
-(defvar coolj-wrap-point nil)
-
-(make-variable-buffer-local 'coolj-wrap-point)
+(defvar-local coolj-wrap-point nil)
(defun coolj-determine-prefix ()
"Determine the prefix for the current line."
-;;; make-deps.el --- compute make dependencies for Elisp sources
+;;; make-deps.el --- compute make dependencies for Elisp sources -*- lexical-binding: t -*-
;;
;; Copyright © Austin Clements
;;
-;;; notmuch-address.el --- address completion with notmuch
+;;; notmuch-address.el --- address completion with notmuch -*- lexical-binding: t -*-
;;
;; Copyright © David Edmondson
;;
(require 'notmuch-parser)
(require 'notmuch-lib)
(require 'notmuch-company)
-;;
+
(declare-function company-manual-begin "company")
+;;; Cache internals
+
(defvar notmuch-address-last-harvest 0
"Time of last address harvest.")
This variable is set by calling `notmuch-address-harvest'.")
(defvar notmuch-address-full-harvest-finished nil
- "t indicates that full completion address harvesting has been finished.
-Use notmuch-address--harvest-ready to access as that will load a
-saved hash if necessary (and available).")
+ "Whether full completion address harvesting has finished.
+Use `notmuch-address--harvest-ready' to access as that will load
+a saved hash if necessary (and available).")
(defun notmuch-address--harvest-ready ()
"Return t if there is a full address hash available.
(or notmuch-address-full-harvest-finished
(notmuch-address--load-address-hash)))
+;;; Options
+
(defcustom notmuch-address-command 'internal
"Determines how address completion candidates are generated.
-If it is a string then that string should be an external program
-which must take a single argument (searched string) and output a
-list of completion candidates, one per line.
+If this is a string, then that string should be an external
+program, which must take a single argument (searched string)
+and output a list of completion candidates, one per line.
+
+If this is the symbol `internal', then an implementation is used
+that relies on the \"notmuch address\" command, but does not use
+any third-party (i.e. \"external\") programs.
-Alternatively, it can be the symbol `internal', in which case
-internal completion is used; the variable
-`notmuch-address-internal-completion' can be used to customize
-this case.
+If this is the symbol `as-is', then Notmuch does not modify the
+value of `message-completion-alist'. This option has to be set to
+this value before `notmuch' is loaded, otherwise the modification
+to `message-completion-alist' may already have taken place. This
+setting obviously does not prevent `message-completion-alist'
+from being modified at all; the user or some third-party package
+may still modify it.
-Finally, if this variable is nil then address completion is
-disabled."
+Finally, if this is nil, then address completion is disabled."
:type '(radio
- (const :tag "Use internal address completion" internal)
- (const :tag "Disable address completion" nil)
- (string :tag "Use external completion command"))
+ (const :tag "Use internal address completion" internal)
+ (string :tag "Use external completion command")
+ (const :tag "Disable address completion" nil)
+ (const :tag "Use default or third-party mechanism" as-is))
:group 'notmuch-send
:group 'notmuch-address
:group 'notmuch-external)
:group 'notmuch-address
:group 'notmuch-hooks)
+(defcustom notmuch-address-use-company t
+ "If available, use company mode for address completion."
+ :type 'boolean
+ :group 'notmuch-send
+ :group 'notmuch-address)
+
+;;; Setup
+
(defun notmuch-address-selection-function (prompt collection initial-input)
"Call (`completing-read'
PROMPT COLLECTION nil nil INITIAL-INPUT 'notmuch-address-history)"
(defun notmuch-address-message-insinuate ()
(message "calling notmuch-address-message-insinuate is no longer needed"))
-(defcustom notmuch-address-use-company t
- "If available, use company mode for address completion."
- :type 'boolean
- :group 'notmuch-send
- :group 'notmuch-address)
-
(defun notmuch-address-setup ()
- (let* ((setup-company (and notmuch-address-use-company
- (require 'company nil t)))
- (pair (cons notmuch-address-completion-headers-regexp
- #'notmuch-address-expand-name)))
- (when setup-company
+ (unless (eq notmuch-address-command 'as-is)
+ (when (and notmuch-address-use-company
+ (require 'company nil t))
(notmuch-company-setup))
- (unless (member pair message-completion-alist)
- (setq message-completion-alist
- (push pair message-completion-alist)))))
+ (cl-pushnew (cons notmuch-address-completion-headers-regexp
+ #'notmuch-address-expand-name)
+ message-completion-alist :test #'equal)))
(defun notmuch-address-toggle-internal-completion ()
"Toggle use of internal completion for current buffer.
(kill-local-variable 'company-idle-delay)
(setq-local company-idle-delay nil))))
+;;; Completion
+
(defun notmuch-address-matching (substring)
"Returns a list of completion candidates matching SUBSTRING.
The candidates are taken from `notmuch-address-completions'."
(let ((candidates)
(re (regexp-quote substring)))
- (maphash (lambda (key val)
+ (maphash (lambda (key _val)
(when (string-match re key)
(push key candidates)))
notmuch-address-completions)
(t
(funcall notmuch-address-selection-function
(format "Address (%s matches): " num-options)
- ;; We put the first match as the initial
- ;; input; we put all the matches as
- ;; possible completions, moving the
- ;; first match to the end of the list
- ;; makes cursor up/down in the list work
- ;; better.
- (append (cdr options) (list (car options)))
- (car options))))))
+ options
+ orig)))))
(if chosen
(progn
(push chosen notmuch-address-history)
(ding))))
(t nil)))
-;; Copied from `w3m-which-command'.
-(defun notmuch-address-locate-command (command)
- "Return non-nil if `command' is an executable either on
-`exec-path' or an absolute pathname."
- (and (stringp command)
- (if (and (file-name-absolute-p command)
- (file-executable-p command))
- command
- (setq command (file-name-nondirectory command))
- (catch 'found-command
- (let (bin)
- (dolist (dir exec-path)
- (setq bin (expand-file-name command dir))
- (when (or (and (file-executable-p bin)
- (not (file-directory-p bin)))
- (and (file-executable-p (setq bin (concat bin ".exe")))
- (not (file-directory-p bin))))
- (throw 'found-command bin))))))))
+;;; Harvest
(defun notmuch-address-harvest-addr (result)
- (let ((name-addr (plist-get result :name-addr)))
- (puthash name-addr t notmuch-address-completions)))
-
-(defun notmuch-address-harvest-handle-result (obj)
- (notmuch-address-harvest-addr obj))
+ (puthash (plist-get result :name-addr)
+ t notmuch-address-completions))
(defun notmuch-address-harvest-filter (proc string)
(when (buffer-live-p (process-buffer proc))
(goto-char (point-max))
(insert string))
(notmuch-sexp-parse-partial-list
- 'notmuch-address-harvest-handle-result (process-buffer proc)))))
+ 'notmuch-address-harvest-addr (process-buffer proc)))))
(defvar notmuch-address-harvest-procs '(nil . nil)
"The currently running harvests.
"Collect addresses completion candidates.
It queries the notmuch database for messages sent/received (as
-configured with `notmuch-address-command`) by the user, collects
+configured with `notmuch-address-command') by the user, collects
destination/source addresses from those messages and stores them
in `notmuch-address-completions'.
(defun notmuch-address--save-address-hash ()
(when notmuch-address-save-filename
(if (or (not (file-exists-p notmuch-address-save-filename))
- ;; The file exists, check it is a file we saved
+ ;; The file exists, check it is a file we saved.
(notmuch-address--get-address-hash))
(with-temp-file notmuch-address-save-filename
(let ((save-plist
(setq notmuch-address-last-harvest now)
(notmuch-address-harvest
nil nil
- (lambda (proc event)
+ (lambda (_proc event)
;; If harvest fails, we want to try
- ;; again when the trigger is next
- ;; called
+ ;; again when the trigger is next called.
(if (string= event "finished\n")
(progn
(notmuch-address--save-address-hash)
(setq notmuch-address-full-harvest-finished t))
(setq notmuch-address-last-harvest 0)))))))
-;;
+;;; Standalone completion
(defun notmuch-address-from-minibuffer (prompt)
(if (not notmuch-address-command)
(let ((minibuffer-local-map rmap))
(read-string prompt)))))
-;;
+;;; _
(provide 'notmuch-address)
;;; Code:
-(eval-when-compile (require 'cl-lib))
-
(require 'notmuch-lib)
-(defvar notmuch-company-last-prefix nil)
-(make-variable-buffer-local 'notmuch-company-last-prefix)
+(defvar-local notmuch-company-last-prefix nil)
+
(declare-function company-begin-backend "company")
(declare-function company-grab "company")
(declare-function company-mode "company")
;;;###autoload
(defun notmuch-company-setup ()
(company-mode)
- (make-local-variable 'company-backends)
- (setq company-backends '(notmuch-company))
+ (setq-local company-backends '(notmuch-company))
;; Disable automatic company completion unless an internal
;; completion method is configured. Company completion (using
;; internal completion) can still be accessed via standard company
(run-hook-with-args 'notmuch-address-post-completion-functions arg))
(no-cache t))))
-
(provide 'notmuch-company)
;;; notmuch-company.el ends here
-;;; notmuch-compat.el --- compatibility functions for earlier versions of emacs
+;;; notmuch-compat.el --- compatibility functions for earlier versions of emacs -*- lexical-binding: t -*-
;;
;; The functions in this file are copied from more modern versions of
;; emacs and are Copyright (C) 1985-1986, 1992, 1994-1995, 1999-2017
;;; Code:
-;; emacs master has a bugfix for folding long headers when sending
-;; messages. Include the fix for earlier versions of emacs. To avoid
-;; interfering with gnus we only run the hook when called from
-;; notmuch-message-mode.
+;; Before Emacs 26.1 lines that are longer than 998 octets were not.
+;; folded. Commit 77bbca8c82f6e553c42abbfafca28f55fc995d00 fixed
+;; that. Until we drop support for Emacs 25 we have to backport that
+;; fix. To avoid interfering with Gnus we only run the hook when
+;; called from notmuch-message-mode.
(declare-function mail-header-fold-field "mail-parse" nil)
(unless (fboundp 'message--fold-long-headers)
(add-hook 'message-header-hook 'notmuch-message--fold-long-headers))
-;; End of compatibility functions
+;; `dlet' isn't available until Emacs 28.1. Below is a copy, with the
+;; addition of `with-no-warnings'.
+(defmacro notmuch-dlet (binders &rest body)
+ "Like `let*' but using dynamic scoping."
+ (declare (indent 1) (debug let))
+ `(let (_)
+ (with-no-warnings ; Quiet "lacks a prefix" warning.
+ ,@(mapcar (lambda (binder)
+ `(defvar ,(if (consp binder) (car binder) binder)))
+ binders))
+ (let* ,binders ,@body)))
(provide 'notmuch-compat)
-;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata
+;;; notmuch-crypto.el --- functions for handling display of cryptographic metadata -*- lexical-binding: t -*-
;;
;; Copyright © Jameson Rollins
;;
(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
+;;; Options
+
(defcustom notmuch-crypto-process-mime t
- "Should cryptographic MIME parts be processed?
+ "Whether to process cryptographic MIME parts.
If this variable is non-nil signatures in multipart/signed
messages will be verified and multipart/encrypted parts will be
:group 'notmuch-crypto)
(defcustom notmuch-crypto-get-keys-asynchronously t
- "Retrieve gpg keys asynchronously."
+ "Whether to retrieve openpgp keys asynchronously."
:type 'boolean
:group 'notmuch-crypto)
:type 'string
:group 'notmuch-crypto)
+;;; Faces
+
(defface notmuch-crypto-part-header
'((((class color)
(background dark))
:group 'notmuch-crypto
:group 'notmuch-faces)
+;;; Functions
+
(define-button-type 'notmuch-crypto-status-button-type
- 'action (lambda (button) (message (button-get button 'help-echo)))
+ 'action (lambda (button) (message "%s" (button-get button 'help-echo)))
'follow-link t
'help-echo "Set notmuch-crypto-process-mime to process cryptographic mime parts."
:supertype 'notmuch-button-type)
(defun notmuch-crypto-insert-sigstatus-button (sigstatus from)
- "Insert a button describing the signature status SIGSTATUS sent
-by user FROM."
+ "Insert a button describing the signature status SIGSTATUS sent by user FROM."
(let* ((status (plist-get sigstatus :status))
(show-button t)
(face 'notmuch-crypto-signature-unknown)
(declare-function notmuch-show-refresh-view "notmuch-show" (&optional reset-state))
(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
-(defun notmuch-crypto--async-key-sentinel (process event)
+(defun notmuch-crypto--async-key-sentinel (process _event)
"When the user asks for a GPG key to be retrieved
asynchronously, handle completion of that task.
'mouse-face 'notmuch-crypto-decryption)
(insert "\n"))
-;;
+;;; _
(provide 'notmuch-crypto)
-;;; notmuch-draft.el --- functions for postponing and editing drafts
+;;; notmuch-draft.el --- functions for postponing and editing drafts -*- lexical-binding: t -*-
;;
;; Copyright © Mark Walters
;; Copyright © David Bremner
;;; Code:
+(require 'cl-lib)
+(require 'pcase)
+(require 'subr-x)
+
(require 'notmuch-maildir-fcc)
(require 'notmuch-tag)
(declare-function notmuch-show-get-message-id "notmuch-show" (&optional bare))
(declare-function notmuch-message-mode "notmuch-mua")
+;;; Options
+
(defgroup notmuch-draft nil
"Saving and editing drafts in Notmuch."
:group 'notmuch)
:group 'notmuch-send)
(defcustom notmuch-draft-save-plaintext 'ask
- "Should notmuch save/postpone in plaintext messages that seem
-like they are intended to be sent encrypted
-(i.e with an mml encryption tag in it)."
+ "Whether to allow saving plaintext when it seems encryption is intended.
+When a message contains mml tags, then that suggest it is
+intended to be encrypted. If the user requests that such a
+message is saved locally, then this option controls whether
+that is allowed. Beside a boolean, this can also be `ask'."
:type '(radio
(const :tag "Never" nil)
(const :tag "Ask every time" ask)
:group 'notmuch-draft
:group 'notmuch-crypto)
+;;; Internal
+
(defvar notmuch-draft-encryption-tag-regex
"<#\\(part encrypt\\|secure.*mode=.*encrypt>\\)"
"Regular expression matching mml tags indicating encryption of part or message.")
-(defvar notmuch-draft-id nil
+(defvar-local notmuch-draft-id nil
"Message-id of the most recent saved draft of this message.")
-(make-variable-buffer-local 'notmuch-draft-id)
(defun notmuch-draft--mark-deleted ()
"Tag the last saved draft deleted.
(notmuch-tag notmuch-draft-id '("+deleted"))))
(defun notmuch-draft-quote-some-mml ()
- "Quote the mml tags in `notmuch-draft-quoted-tags`."
+ "Quote the mml tags in `notmuch-draft-quoted-tags'."
(save-excursion
;; First we deal with any secure tag separately.
(message-goto-body)
(insert "!"))))))
(defun notmuch-draft-unquote-some-mml ()
- "Unquote the mml tags in `notmuch-draft-quoted-tags`."
+ "Unquote the mml tags in `notmuch-draft-quoted-tags'."
(save-excursion
(when notmuch-draft-quoted-tags
(let ((re (concat "<#!+/?\\("
(let (secure-tag)
(save-restriction
(message-narrow-to-headers)
- (setq secure-tag (message-fetch-field "X-Notmuch-Emacs-Secure" 't))
+ (setq secure-tag (message-fetch-field "X-Notmuch-Emacs-Secure" t))
(message-remove-header "X-Notmuch-Emacs-Secure"))
(message-goto-body)
(when secure-tag
(insert secure-tag "\n")))))
(defun notmuch-draft--has-encryption-tag ()
- "Returns t if there is an mml secure tag."
+ "Return non-nil if there is an mml secure tag."
(save-excursion
(message-goto-body)
- (re-search-forward notmuch-draft-encryption-tag-regex nil 't)))
+ (re-search-forward notmuch-draft-encryption-tag-regex nil t)))
(defun notmuch-draft--query-encryption ()
- "Checks if we should save a message that should be encrypted.
+ "Return non-nil if we should save a message that should be encrypted.
`notmuch-draft-save-plaintext' controls the behaviour."
(cl-case notmuch-draft-save-plaintext
;; but notmuch doesn't want that form, so remove them.
(concat "draft-" (substring (message-make-message-id) 1 -1)))
+;;; Commands
+
(defun notmuch-draft-save ()
"Save the current draft message in the notmuch database.
This saves the current message in the database with tags
-`notmuch-draft-tags` (in addition to any default tags
+`notmuch-draft-tags' (in addition to any default tags
applied to newly inserted messages)."
(interactive)
(when (notmuch-draft--has-encryption-tag)
;; so that it is easier to search for the message, and the
;; latter so we have a way of accessing the saved message (for
;; example to delete it at a later time). We check that the
- ;; user has these in `message-deletable-headers` (the default)
+ ;; user has these in `message-deletable-headers' (the default)
;; as otherwise they are doing something strange and we
;; shouldn't interfere. Note, since we are doing this in a new
;; buffer we don't change the version in the compose buffer.
(notmuch-draft-quote-some-mml)
(notmuch-maildir-setup-message-for-saving)
(notmuch-maildir-notmuch-insert-current-buffer
- notmuch-draft-folder 't notmuch-draft-tags))
+ notmuch-draft-folder t notmuch-draft-tags))
;; We are now back in the original compose buffer. Note the
;; function notmuch-call-notmuch-process (called by
;; notmuch-maildir-notmuch-insert-current-buffer) signals an error
(defun notmuch-draft-resume (id)
"Resume editing of message with id ID."
+ ;; Used by command `notmuch-show-resume-message'.
(let* ((tags (process-lines notmuch-command "search" "--output=tags"
"--exclude=false" id))
(draft (equal tags (notmuch-update-tags tags notmuch-draft-tags))))
;; message is resaved or sent.
(setq notmuch-draft-id (and draft id)))))
+;;; _
(add-hook 'message-send-hook 'notmuch-draft--mark-deleted)
-
(provide 'notmuch-draft)
;;; notmuch-draft.el ends here
-;;; notmuch-hello.el --- welcome to notmuch, a frontend
+;;; notmuch-hello.el --- welcome to notmuch, a frontend -*- lexical-binding: t -*-
;;
;; Copyright © David Edmondson
;;
;;; Code:
-(eval-when-compile (require 'cl-lib))
-
(require 'widget)
(require 'wid-edit) ; For `widget-forward'.
(&optional query query-context target buffer-name open-target))
+;;; Options
+
(defun notmuch-saved-search-get (saved-search field)
"Get FIELD from SAVED-SEARCH.
shown. If not present then the :query property
is used.
:sort-order Specify the sort order to be used for the search.
- Possible values are 'oldest-first 'newest-first or
- nil. Nil means use the default sort order.
+ Possible values are `oldest-first', `newest-first'
+ or nil. Nil means use the default sort order.
:search-type Specify whether to run the search in search-mode,
tree mode or unthreaded mode. Set to 'tree to specify tree
mode, 'unthreaded to specify unthreaded mode, and set to nil
(defvar notmuch-hello-indent 4
"How much to indent non-headers.")
+(defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png")))
+
(defcustom notmuch-show-logo t
"Should the notmuch logo be shown?"
:type 'boolean
:group 'notmuch-hello
:group 'notmuch-hooks)
-(defvar notmuch-hello-url "https://notmuchmail.org"
+(defconst notmuch-hello-url "https://notmuchmail.org"
"The `notmuch' web site.")
(defvar notmuch-hello-custom-section-options
:group 'notmuch-hello
:type 'boolean)
+;;; Internal variables
+
(defvar notmuch-hello-hidden-sections nil
"List of sections titles whose contents are hidden.")
(defvar notmuch-hello-first-run t
- "True if `notmuch-hello' is run for the first time, set to nil
-afterwards.")
-
-(defun notmuch-hello-nice-number (n)
- (let (result)
- (while (> n 0)
- (push (% n 1000) result)
- (setq n (/ n 1000)))
- (setq result (or result '(0)))
- (apply #'concat
- (number-to-string (car result))
- (mapcar (lambda (elem)
- (format "%s%03d" notmuch-hello-thousands-separator elem))
- (cdr result)))))
-
-(defun notmuch-hello-trim (search)
- "Trim whitespace."
- (if (string-match "^[[:space:]]*\\(.*[^[:space:]]\\)[[:space:]]*$" search)
- (match-string 1 search)
- search))
-
-(defun notmuch-hello-search (&optional search)
- (unless (null search)
- (setq search (notmuch-hello-trim search))
- (let ((history-delete-duplicates t))
- (add-to-history 'notmuch-search-history search)))
- (notmuch-search search notmuch-search-oldest-first))
-
-(defun notmuch-hello-add-saved-search (widget)
- (interactive)
- (let ((search (widget-value
- (symbol-value
- (widget-get widget :notmuch-saved-search-widget))))
+ "True if `notmuch-hello' is run for the first time, set to nil afterwards.")
+
+;;; Widgets for inserters
+
+(define-widget 'notmuch-search-item 'item
+ "A recent search."
+ :format "%v\n"
+ :value-create 'notmuch-search-item-value-create)
+
+(defun notmuch-search-item-value-create (widget)
+ (let ((value (widget-get widget :value)))
+ (widget-insert (make-string notmuch-hello-indent ?\s))
+ (widget-create 'editable-field
+ :size (widget-get widget :size)
+ :parent widget
+ :action #'notmuch-hello-search
+ value)
+ (widget-insert " ")
+ (widget-create 'push-button
+ :parent widget
+ :notify #'notmuch-hello-add-saved-search
+ "save")
+ (widget-insert " ")
+ (widget-create 'push-button
+ :parent widget
+ :notify #'notmuch-hello-delete-search-from-history
+ "del")))
+
+(defun notmuch-search-item-field-width ()
+ (max 8 ; Don't let the search boxes be less than 8 characters wide.
+ (- (window-width)
+ notmuch-hello-indent ; space at bol
+ notmuch-hello-indent ; space at eol
+ 1 ; for the space before the [save] button
+ 6 ; for the [save] button
+ 1 ; for the space before the [del] button
+ 5))) ; for the [del] button
+
+;;; Widget actions
+
+(defun notmuch-hello-search (widget &rest _event)
+ (let ((search (widget-value widget)))
+ (when search
+ (setq search (string-trim search))
+ (let ((history-delete-duplicates t))
+ (add-to-history 'notmuch-search-history search)))
+ (notmuch-search search notmuch-search-oldest-first)))
+
+(defun notmuch-hello-add-saved-search (widget &rest _event)
+ (let ((search (widget-value (widget-get widget :parent)))
(name (completing-read "Name for saved search: "
notmuch-saved-searches)))
;; If an existing saved search with this name exists, remove it.
(setq notmuch-saved-searches
(cl-loop for elem in notmuch-saved-searches
- if (not (equal name
- (notmuch-saved-search-get elem :name)))
+ unless (equal name (notmuch-saved-search-get elem :name))
collect elem))
;; Add the new one.
(customize-save-variable 'notmuch-saved-searches
(message "Saved '%s' as '%s'." search name)
(notmuch-hello-update)))
-(defun notmuch-hello-delete-search-from-history (widget)
- (interactive)
- (let ((search (widget-value
- (symbol-value
- (widget-get widget :notmuch-saved-search-widget)))))
- (setq notmuch-search-history (delete search
- notmuch-search-history))
+(defun notmuch-hello-delete-search-from-history (widget &rest _event)
+ (when (y-or-n-p "Are you sure you want to delete this search? ")
+ (let ((search (widget-value (widget-get widget :parent))))
+ (setq notmuch-search-history
+ (delete search notmuch-search-history)))
(notmuch-hello-update)))
+;;; Button utilities
+
+;; `notmuch-hello-query-counts', `notmuch-hello-nice-number' and
+;; `notmuch-hello-insert-buttons' are used outside this section.
+;; All other functions that are defined in this section are only
+;; used by these two functions.
+
(defun notmuch-hello-longest-label (searches-alist)
(or (cl-loop for elem in searches-alist
maximize (length (notmuch-saved-search-get elem :name)))
(cl-loop for row from 0 to (- nrows 1)
append (notmuch-hello-reflect-generate-row ncols nrows row list))))
-(defun notmuch-hello-widget-search (widget &rest ignore)
- (cond
- ((eq (widget-get widget :notmuch-search-type) 'tree)
- (notmuch-tree (widget-get widget
- :notmuch-search-terms)))
- ((eq (widget-get widget :notmuch-search-type) 'unthreaded)
- (notmuch-unthreaded (widget-get widget
- :notmuch-search-terms)))
+(defun notmuch-hello-widget-search (widget &rest _ignore)
+ (cl-case (widget-get widget :notmuch-search-type)
+ (tree
+ (notmuch-tree (widget-get widget :notmuch-search-terms)))
+ (unthreaded
+ (notmuch-unthreaded (widget-get widget :notmuch-search-terms)))
(t
- (notmuch-search (widget-get widget
- :notmuch-search-terms)
- (widget-get widget
- :notmuch-search-oldest-first)))))
+ (notmuch-search (widget-get widget :notmuch-search-terms)
+ (widget-get widget :notmuch-search-oldest-first)))))
(defun notmuch-saved-search-count (search)
(car (process-lines notmuch-command "count" search)))
--batch'. In general we recommend running matching versions of
the CLI and emacs interface."))
(goto-char (point-min))
- (notmuch-remove-if-not
- #'identity
- (mapcar
- (lambda (elem)
- (let* ((elem-plist (notmuch-hello-saved-search-to-plist elem))
- (search-query (plist-get elem-plist :query))
- (filtered-query (notmuch-hello-filtered-query
- search-query (plist-get options :filter)))
- (message-count (prog1 (read (current-buffer))
- (forward-line 1))))
- (when (and filtered-query (or (plist-get options :show-empty-searches)
- (> message-count 0)))
- (setq elem-plist (plist-put elem-plist :query filtered-query))
- (plist-put elem-plist :count message-count))))
- query-list))))
+ (cl-mapcan
+ (lambda (elem)
+ (let* ((elem-plist (notmuch-hello-saved-search-to-plist elem))
+ (search-query (plist-get elem-plist :query))
+ (filtered-query (notmuch-hello-filtered-query
+ search-query (plist-get options :filter)))
+ (message-count (prog1 (read (current-buffer))
+ (forward-line 1))))
+ (when (and filtered-query (or (plist-get options :show-empty-searches)
+ (> message-count 0)))
+ (setq elem-plist (plist-put elem-plist :query filtered-query))
+ (list (plist-put elem-plist :count message-count)))))
+ query-list)))
+
+(defun notmuch-hello-nice-number (n)
+ (let (result)
+ (while (> n 0)
+ (push (% n 1000) result)
+ (setq n (/ n 1000)))
+ (setq result (or result '(0)))
+ (apply #'concat
+ (number-to-string (car result))
+ (mapcar (lambda (elem)
+ (format "%s%03d" notmuch-hello-thousands-separator elem))
+ (cdr result)))))
(defun notmuch-hello-insert-buttons (searches)
"Insert buttons for SEARCHES.
(unless (eq (% count tags-per-line) 0)
(widget-insert "\n"))))
-(defimage notmuch-hello-logo ((:type png :file "notmuch-logo.png")))
+;;; Mode
(defun notmuch-hello-update ()
"Update the notmuch-hello buffer."
;; Refresh hello as soon as we get back to redisplay. On Emacs
;; 24, we can't do it right here because something in this
;; hook's call stack overrides hello's point placement.
+ ;; FIXME And on Emacs releases that we still support?
(run-at-time nil nil #'notmuch-hello t))
(unless hello-buf
;; Clean up hook
(remove-hook 'window-configuration-change-hook
#'notmuch-hello-window-configuration-change))))
-;; the following variable is defined as being defconst in notmuch-version.el
-(defvar notmuch-emacs-version)
-
-(defun notmuch-hello-versions ()
- "Display the notmuch version(s)."
- (interactive)
- (let ((notmuch-cli-version (notmuch-cli-version)))
- (message "notmuch version %s"
- (if (string= notmuch-emacs-version notmuch-cli-version)
- notmuch-cli-version
- (concat notmuch-cli-version
- " (emacs mua version " notmuch-emacs-version ")")))))
-
(defvar notmuch-hello-mode-map
- (let ((map (if (fboundp 'make-composed-keymap)
- ;; Inherit both widget-keymap and
- ;; notmuch-common-keymap. We have to use
- ;; make-sparse-keymap to force this to be a new
- ;; keymap (so that when we modify map it does not
- ;; modify widget-keymap).
- (make-composed-keymap (list (make-sparse-keymap) widget-keymap))
- ;; Before Emacs 24, keymaps didn't support multiple
- ;; inheritance,, so just copy the widget keymap since
- ;; it's unlikely to change.
- (copy-keymap widget-keymap))))
+ ;; Inherit both widget-keymap and notmuch-common-keymap. We have
+ ;; to use make-sparse-keymap to force this to be a new keymap (so
+ ;; that when we modify map it does not modify widget-keymap).
+ (let ((map (make-composed-keymap (list (make-sparse-keymap) widget-keymap))))
(set-keymap-parent map notmuch-common-keymap)
- (define-key map "v" 'notmuch-hello-versions)
(define-key map (kbd "<C-tab>") 'widget-backward)
map)
"Keymap for \"notmuch hello\" buffers.")
Complete list of currently available key bindings:
\\{notmuch-hello-mode-map}"
- (setq notmuch-buffer-refresh-function #'notmuch-hello-update)
- ;;(setq buffer-read-only t)
- )
+ (setq notmuch-buffer-refresh-function #'notmuch-hello-update))
+
+;;; Inserters
(defun notmuch-hello-generate-tag-alist (&optional hide-tags)
"Return an alist from tags to queries to display in the all-tags section."
- (mapcar (lambda (tag)
- (cons tag (concat "tag:" (notmuch-escape-boolean-term tag))))
- (notmuch-remove-if-not
- (lambda (tag)
- (not (member tag hide-tags)))
- (process-lines notmuch-command "search" "--output=tags" "*"))))
+ (cl-mapcan (lambda (tag)
+ (and (not (member tag hide-tags))
+ (list (cons tag
+ (concat "tag:"
+ (notmuch-escape-boolean-term tag))))))
+ (process-lines notmuch-command "search" "--output=tags" "*")))
(defun notmuch-hello-insert-header ()
"Insert the default notmuch-hello header."
(let ((widget-link-prefix "")
(widget-link-suffix ""))
(widget-create 'link
- :notify (lambda (&rest ignore)
+ :notify (lambda (&rest _ignore)
(browse-url notmuch-hello-url))
:help-echo "Visit the notmuch website."
"notmuch")
(widget-insert ". ")
(widget-insert "You have ")
(widget-create 'link
- :notify (lambda (&rest ignore)
+ :notify (lambda (&rest _ignore)
(notmuch-hello-update))
:help-echo "Refresh"
(notmuch-hello-nice-number
(when searches
(widget-insert "Saved searches: ")
(widget-create 'push-button
- :notify (lambda (&rest ignore)
+ :notify (lambda (&rest _ignore)
(customize-variable 'notmuch-saved-searches))
"edit")
(widget-insert "\n\n")
;; search boxes.
:size (max 8 (- (window-width) notmuch-hello-indent
(length "Search: ")))
- :action (lambda (widget &rest ignore)
- (notmuch-hello-search (widget-value widget))))
+ :action #'notmuch-hello-search)
;; Add an invisible dot to make `widget-end-of-line' ignore
;; trailing spaces in the search widget field. A dot is used
;; instead of a space to make `show-trailing-whitespace'
;; happy, i.e. avoid it marking the whole line as trailing
;; spaces.
- (widget-insert ".")
- (put-text-property (1- (point)) (point) 'invisible t)
+ (widget-insert (propertize "." 'invisible t))
(widget-insert "\n"))
(defun notmuch-hello-insert-recent-searches ()
"Insert recent searches."
(when notmuch-search-history
(widget-insert "Recent searches: ")
- (widget-create 'push-button
- :notify (lambda (&rest ignore)
- (when (y-or-n-p "Are you sure you want to clear the searches? ")
- (setq notmuch-search-history nil)
- (notmuch-hello-update)))
- "clear")
+ (widget-create
+ 'push-button
+ :notify (lambda (&rest _ignore)
+ (when (y-or-n-p "Are you sure you want to clear the searches? ")
+ (setq notmuch-search-history nil)
+ (notmuch-hello-update)))
+ "clear")
(widget-insert "\n\n")
- (let ((start (point)))
- (cl-loop for i from 1 to notmuch-hello-recent-searches-max
- for search in notmuch-search-history do
- (let ((widget-symbol (intern (format "notmuch-hello-search-%d" i))))
- (set widget-symbol
- (widget-create 'editable-field
- ;; Don't let the search boxes be
- ;; less than 8 characters wide.
- :size (max 8
- (- (window-width)
- ;; Leave some space
- ;; at the start and
- ;; end of the
- ;; boxes.
- (* 2 notmuch-hello-indent)
- ;; 1 for the space
- ;; before the
- ;; `[save]' button. 6
- ;; for the `[save]'
- ;; button.
- 1 6
- ;; 1 for the space
- ;; before the `[del]'
- ;; button. 5 for the
- ;; `[del]' button.
- 1 5))
- :action (lambda (widget &rest ignore)
- (notmuch-hello-search (widget-value widget)))
- search))
- (widget-insert " ")
- (widget-create 'push-button
- :notify (lambda (widget &rest ignore)
- (notmuch-hello-add-saved-search widget))
- :notmuch-saved-search-widget widget-symbol
- "save")
- (widget-insert " ")
- (widget-create 'push-button
- :notify (lambda (widget &rest ignore)
- (when (y-or-n-p "Are you sure you want to delete this search? ")
- (notmuch-hello-delete-search-from-history widget)))
- :notmuch-saved-search-widget widget-symbol
- "del"))
- (widget-insert "\n"))
- (indent-rigidly start (point) notmuch-hello-indent))
- nil))
+ (let ((width (notmuch-search-item-field-width)))
+ (dolist (search (seq-take notmuch-search-history
+ notmuch-hello-recent-searches-max))
+ (widget-create 'notmuch-search-item :value search :size width)))))
(defun notmuch-hello-insert-searches (title query-list &rest options)
"Insert a section with TITLE showing a list of buttons made from QUERY-LIST.
(start (point)))
(if is-hidden
(widget-create 'push-button
- :notify `(lambda (widget &rest ignore)
+ :notify `(lambda (widget &rest _ignore)
(setq notmuch-hello-hidden-sections
(delete ,title notmuch-hello-hidden-sections))
(notmuch-hello-update))
"show")
(widget-create 'push-button
- :notify `(lambda (widget &rest ignore)
+ :notify `(lambda (widget &rest _ignore)
(add-to-list 'notmuch-hello-hidden-sections
,title)
(notmuch-hello-update))
(widget-insert "Hit `?' for context-sensitive help in any Notmuch screen.\n")
(widget-insert "Customize ")
(widget-create 'link
- :notify (lambda (&rest ignore)
+ :notify (lambda (&rest _ignore)
(customize-group 'notmuch))
:button-prefix "" :button-suffix ""
"Notmuch")
(widget-insert " or ")
(widget-create 'link
- :notify (lambda (&rest ignore)
+ :notify (lambda (&rest _ignore)
(customize-variable 'notmuch-hello-sections))
:button-prefix "" :button-suffix ""
"this page.")
(let ((fill-column (- (window-width) notmuch-hello-indent)))
(center-region start (point)))))
+;;; Hello!
+
;;;###autoload
(defun notmuch-hello (&optional no-display)
"Run notmuch and display saved searches, known tags, etc."
(run-hooks 'notmuch-hello-refresh-hook)
(setq notmuch-hello-first-run nil))
-(defun notmuch-folder ()
- "Deprecated function for invoking notmuch---calling `notmuch' is preferred now."
- (interactive)
- (notmuch-hello))
-
-;;
+;;; _
(provide 'notmuch-hello)
-;;; notmuch-jump.el --- User-friendly shortcut keys
+;;; notmuch-jump.el --- User-friendly shortcut keys -*- lexical-binding: t -*-
;;
;; Copyright © Austin Clements
;;
;;; Code:
-(eval-when-compile
- (require 'cl-lib)
- (require 'pcase))
-
(require 'notmuch-lib)
(require 'notmuch-hello)
-(eval-and-compile
- (unless (fboundp 'window-body-width)
- ;; Compatibility for Emacs pre-24
- (defalias 'window-body-width 'window-width)))
-
;;;###autoload
(defun notmuch-jump-search ()
"Jump to a saved search by shortcut key.
(setq action-map (nreverse action-map))
(if action-map
(notmuch-jump action-map "Search: ")
- (error "To use notmuch-jump, \
-please customize shortcut keys in notmuch-saved-searches."))))
+ (error "To use notmuch-jump, %s"
+ "please customize shortcut keys in notmuch-saved-searches."))))
(defvar notmuch-jump--action nil)
buffer."
;; Compute the maximum key description width
(let ((key-width 1))
- (pcase-dolist (`(,key ,desc) action-map)
+ (pcase-dolist (`(,key ,_desc) action-map)
(setq key-width
(max key-width
(string-width (format-kbd-macro key)))))
"Translate ACTION-MAP into a minibuffer keymap."
(let ((map (make-sparse-keymap)))
(set-keymap-parent map notmuch-jump-minibuffer-map)
- (pcase-dolist (`(,key ,name ,fn) action-map)
+ (pcase-dolist (`(,key ,_name ,fn) action-map)
(when (= (length key) 1)
(define-key map key
`(lambda () (interactive)
;; By doing this in two passes (and checking if we already have a
;; binding) we avoid problems if the user specifies a binding which
;; is a prefix of another binding.
- (pcase-dolist (`(,key ,name ,fn) action-map)
+ (pcase-dolist (`(,key ,_name ,_fn) action-map)
(when (> (length key) 1)
(let* ((key (elt key 0))
(keystr (string key))
(exit-minibuffer)))))))
map))
-;;
-
(provide 'notmuch-jump)
;;; notmuch-jump.el ends here
-;;; notmuch-lib.el --- common variables, functions and function declarations
+;;; notmuch-lib.el --- common variables, functions and function declarations -*- lexical-binding: t -*-
;;
;; Copyright © Carl Worth
;;
;;; Code:
(require 'cl-lib)
+(require 'pcase)
+(require 'subr-x)
(require 'mm-util)
(require 'mm-view)
(defconst notmuch-emacs-version "unknown"
"Placeholder variable when notmuch-version.el[c] is not available."))
+;;; Groups
+
(defgroup notmuch nil
"Notmuch mail reader for Emacs."
:group 'mail)
"Graphical attributes for displaying text"
:group 'notmuch)
+;;; Options
+
(defcustom notmuch-command "notmuch"
"Name of the notmuch binary.
search."
:type 'boolean
:group 'notmuch-search)
+(make-variable-buffer-local 'notmuch-search-oldest-first)
(defcustom notmuch-poll-script nil
"[Deprecated] Command to run to incorporate new mail into the notmuch database.
(string :tag "Custom script"))
:group 'notmuch-external)
-;;
-
-(defvar notmuch-search-history nil
- "Variable to store notmuch searches history.")
-
(defcustom notmuch-archive-tags '("-inbox")
"List of tag changes to apply to a message or a thread when it is archived.
:group 'notmuch-search
:group 'notmuch-show)
+;;; Variables
+
+(defvar notmuch-search-history nil
+ "Variable to store notmuch searches history.")
+
(defvar notmuch-common-keymap
(let ((map (make-sparse-keymap)))
(define-key map "?" 'notmuch-help)
+ (define-key map "v" 'notmuch-version)
(define-key map "q" 'notmuch-bury-or-kill-this-buffer)
(define-key map "s" 'notmuch-search)
(define-key map "t" 'notmuch-search-by-tag)
(select-window (posn-window (event-start last-input-event)))
(button-activate button)))
+;;; CLI Utilities
+
(defun notmuch-command-to-string (&rest args)
"Synchronously invoke \"notmuch\" with the given list of arguments.
Otherwise the output will be returned."
(with-temp-buffer
- (let* ((status (apply #'call-process notmuch-command nil t nil args))
- (output (buffer-string)))
+ (let ((status (apply #'call-process notmuch-command nil t nil args))
+ (output (buffer-string)))
(notmuch-check-exit-status status (cons notmuch-command args) output)
output)))
(match-string 2 long-string)
"unknown")))
+(defvar notmuch-emacs-version)
+
+(defun notmuch-version ()
+ "Display the notmuch version.
+The versions of the Emacs package and the `notmuch' executable
+should match, but if and only if they don't, then this command
+displays both values separately."
+ (interactive)
+ (let ((cli-version (notmuch-cli-version)))
+ (message "notmuch version %s"
+ (if (string= notmuch-emacs-version cli-version)
+ cli-version
+ (concat cli-version
+ " (emacs mua version " notmuch-emacs-version ")")))))
+
+;;; Notmuch Configuration
+
(defun notmuch-config-get (item)
"Return a value from the notmuch configuration."
(let* ((val (notmuch-command-to-string "config" "get" item))
(len (length val)))
;; Trim off the trailing newline (if the value is empty or not
- ;; configured, there will be no newline)
- (if (and (> len 0) (= (aref val (- len 1)) ?\n))
+ ;; configured, there will be no newline).
+ (if (and (> len 0)
+ (= (aref val (- len 1)) ?\n))
(substring val 0 -1)
val)))
(defun notmuch-user-emails ()
(cons (notmuch-user-primary-email) (notmuch-user-other-email)))
+;;; Commands
+
(defun notmuch-poll ()
"Run \"notmuch new\" or an external script to import mail.
(interactive)
(message "Polling mail...")
(if (stringp notmuch-poll-script)
- (unless (string= notmuch-poll-script "")
+ (unless (string-empty-p notmuch-poll-script)
(unless (equal (call-process notmuch-poll-script nil nil) 0)
(error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
(notmuch-call-notmuch-process "new"))
(bury-buffer)
(kill-buffer)))
-(defun notmuch-documentation-first-line (symbol)
- "Return the first line of the documentation string for SYMBOL."
- (let ((doc (documentation symbol)))
- (if doc
- (with-temp-buffer
- (insert (documentation symbol t))
- (goto-char (point-min))
- (let ((beg (point)))
- (end-of-line)
- (buffer-substring beg (point))))
- "")))
+;;; Describe Key Bindings
(defun notmuch-prefix-key-description (key)
"Given a prefix key code, return a human-readable string representation.
"M-"
(concat desc " "))))
-
(defun notmuch-describe-key (actual-key binding prefix ua-keys tail)
"Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL.
(or (and (symbolp binding)
(get binding 'notmuch-doc))
(and (functionp binding)
- (notmuch-documentation-first-line binding))))
+ (let ((doc (documentation binding)))
+ (and doc
+ (string-match "\\`.+" doc)
+ (match-string 0 doc))))))
tail)))
tail)
its prefixed behavior by setting the 'notmuch-prefix-doc property
of its command symbol."
(interactive)
- (let* ((mode major-mode)
- (doc (substitute-command-keys
- (notmuch-substitute-command-keys (documentation mode t)))))
+ (let ((doc (substitute-command-keys
+ (notmuch-substitute-command-keys
+ (documentation major-mode t)))))
(with-current-buffer (generate-new-buffer "*notmuch-help*")
(insert doc)
(goto-char (point-min))
(insert desc)))
(pop-to-buffer (help-buffer)))))
-(defvar notmuch-buffer-refresh-function nil
+;;; Refreshing Buffers
+
+(defvar-local notmuch-buffer-refresh-function nil
"Function to call to refresh the current buffer.")
-(make-variable-buffer-local 'notmuch-buffer-refresh-function)
(defun notmuch-refresh-this-buffer ()
"Refresh the current buffer."
(with-current-buffer buffer
(notmuch-refresh-this-buffer))))))
+;;; String Utilities
+
(defun notmuch-prettify-subject (subject)
- ;; This function is used by `notmuch-search-process-filter' which
- ;; requires that we not disrupt its' matching state.
+ ;; This function is used by `notmuch-search-process-filter',
+ ;; which requires that we not disrupt its matching state.
(save-match-data
(if (and subject
(string-match "^[ \t]*$" subject))
(replace-regexp-in-string
"[ %\"]" (lambda (match) (format "%%%02x" (aref match 0))) str))
-;;
-
(defun notmuch-common-do-stash (text)
"Common function to stash text in kill ring, and display in minibuffer."
(if text
(kill-new "")
(message "Nothing to stash!")))
-;;
-
-(defun notmuch-remove-if-not (predicate list)
- "Return a copy of LIST with all items not satisfying PREDICATE removed."
- (let (out)
- (while list
- (when (funcall predicate (car list))
- (push (car list) out))
- (setq list (cdr list)))
- (nreverse out)))
+;;; Generic Utilities
(defun notmuch-plist-delete (plist property)
- (let* ((xplist (cons nil plist))
- (pred xplist))
- (while (cdr pred)
- (when (eq (cadr pred) property)
- (setcdr pred (cdddr pred)))
- (setq pred (cddr pred)))
- (cdr xplist)))
-
-(defun notmuch-split-content-type (content-type)
- "Split content/type into 'content' and 'type'."
- (split-string content-type "/"))
+ (let (p)
+ (while plist
+ (unless (eq property (car plist))
+ (setq p (plist-put p (car plist) (cadr plist))))
+ (setq plist (cddr plist)))
+ p))
+
+;;; MML Utilities
(defun notmuch-match-content-type (t1 t2)
- "Return t if t1 and t2 are matching content types, taking wildcards into account."
- (let ((st1 (notmuch-split-content-type t1))
- (st2 (notmuch-split-content-type t2)))
- (if (or (string= (cadr st1) "*")
- (string= (cadr st2) "*"))
- ;; Comparison of content types should be case insensitive.
- (string= (downcase (car st1)) (downcase (car st2)))
- (string= (downcase t1) (downcase t2)))))
+ "Return t if t1 and t2 are matching content types.
+Take wildcards into account."
+ (and (stringp t1)
+ (stringp t2)
+ (let ((st1 (split-string t1 "/"))
+ (st2 (split-string t2 "/")))
+ (if (or (string= (cadr st1) "*")
+ (string= (cadr st2) "*"))
+ ;; Comparison of content types should be case insensitive.
+ (string= (downcase (car st1))
+ (downcase (car st2)))
+ (string= (downcase t1)
+ (downcase t2))))))
(defvar notmuch-multipart/alternative-discouraged
'(;; Avoid HTML parts.
MSG (if it isn't already)."
(notmuch--get-bodypart-raw msg part process-crypto nil cache))
-;; Workaround: The call to `mm-display-part' below triggers a bug in
-;; Emacs 24 if it attempts to use the shr renderer to display an HTML
-;; part with images in it (demonstrated in 24.1 and 24.2 on Debian and
-;; Fedora 17, though unreproducible in other configurations).
-;; `mm-shr' references the variable `gnus-inhibit-images' without
-;; first loading gnus-art, which defines it, resulting in a
-;; void-variable error. Hence, we advise `mm-shr' to ensure gnus-art
-;; is loaded.
-(define-advice mm-shr (:before (_handle) notmuch--load-gnus-args)
- "Require `gnus-art' since we use its variables."
- (require 'gnus-art nil t))
-
(defun notmuch-mm-display-part-inline (msg part content-type process-crypto)
"Use the mm-decode/mm-view functions to display a part in the
current buffer, if possible."
(mm-display-part handle)
t))))))
+;;; Generic Utilities
+
;; Converts a plist of headers to an alist of headers. The input plist should
;; have symbols of the form :Header as keys, and the resulting alist will have
;; symbols of the form 'Header as keys.
(put-text-property start next prop (funcall func value) object)
(setq start next))))
+;;; Running Notmuch
+
(defun notmuch-logged-error (msg &optional extra)
"Log MSG and EXTRA to *Notmuch errors* and signal MSG.
Emacs requested a newer output format than supported by the notmuch CLI.
You may need to restart Emacs or upgrade your notmuch package."))
(t
- (let* ((command-string
- (mapconcat (lambda (arg)
- (shell-quote-argument
- (cond ((stringp arg) arg)
- ((symbolp arg) (symbol-name arg))
- (t "*UNKNOWN ARGUMENT*"))))
- command " "))
- (extra
- (concat "command: " command-string "\n"
- (if (integerp exit-status)
- (format "exit status: %s\n" exit-status)
- (format "exit signal: %s\n" exit-status))
- (and err (concat "stderr:\n" err))
- (and output (concat "stdout:\n" output)))))
+ (pcase-let*
+ ((`(,command . ,args) command)
+ (command (if (equal (file-name-nondirectory command)
+ notmuch-command)
+ notmuch-command
+ command))
+ (command-string
+ (mapconcat (lambda (arg)
+ (shell-quote-argument
+ (cond ((stringp arg) arg)
+ ((symbolp arg) (symbol-name arg))
+ (t "*UNKNOWN ARGUMENT*"))))
+ (cons command args)
+ " "))
+ (extra
+ (concat "command: " command-string "\n"
+ (if (integerp exit-status)
+ (format "exit status: %s\n" exit-status)
+ (format "exit signal: %s\n" exit-status))
+ (and err (concat "stderr:\n" err))
+ (and output (concat "stdout:\n" output)))))
(if err
;; We have an error message straight from the CLI.
(notmuch-logged-error
;; We only have combined output from the CLI; don't inundate
;; the user with it. Mimic `process-lines'.
(notmuch-logged-error (format "%s exited with status %s"
- (car command) exit-status)
+ command exit-status)
extra))
;; `notmuch-logged-error' does not return.
))))
invoke `set-process-sentinel' directly on the returned process,
as that will interfere with the handling of stderr and the exit
status."
- (let (err-file err-buffer proc err-proc
- ;; Find notmuch using Emacs' `exec-path'
- (command (or (executable-find notmuch-command)
- (error "Command not found: %s" notmuch-command))))
- (if (fboundp 'make-process)
- (progn
- (setq err-buffer (generate-new-buffer " *notmuch-stderr*"))
- ;; Emacs 25 and newer has `make-process', which allows
- ;; redirecting stderr independently from stdout to a
- ;; separate buffer. As this allows us to avoid using a
- ;; temporary file and shell invocation, use it when
- ;; available.
- (setq proc (make-process
- :name name
- :buffer buffer
- :command (cons command args)
- :connection-type 'pipe
- :stderr err-buffer))
- (setq err-proc (get-buffer-process err-buffer))
- (process-put proc 'err-buffer err-buffer)
-
- (process-put err-proc 'err-file err-file)
- (process-put err-proc 'err-buffer err-buffer)
- (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel))
- ;; On Emacs versions before 25, there is no way to capture
- ;; stdout and stderr separately for asynchronous processes, or
- ;; even to redirect stderr to a file, so we use a trivial shell
- ;; wrapper to send stderr to a temporary file and clean things
- ;; up in the sentinel.
- (setq err-file (make-temp-file "nmerr"))
- (let ((process-connection-type nil)) ;; Use a pipe
- (setq proc (apply #'start-process name buffer
- "/bin/sh" "-c"
- "exec 2>\"$1\"; shift; exec \"$0\" \"$@\""
- command err-file args)))
- (process-put proc 'err-file err-file))
+ (let* ((command (or (executable-find notmuch-command)
+ (error "Command not found: %s" notmuch-command)))
+ (err-buffer (generate-new-buffer " *notmuch-stderr*"))
+ (proc (make-process
+ :name name
+ :buffer buffer
+ :command (cons command args)
+ :connection-type 'pipe
+ :stderr err-buffer))
+ (err-proc (get-buffer-process err-buffer)))
+ (process-put proc 'err-buffer err-buffer)
(process-put proc 'sub-sentinel sentinel)
- (process-put proc 'real-command (cons notmuch-command args))
(set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
+ (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel)
proc))
(defun notmuch-start-notmuch-sentinel (proc event)
"Process sentinel function used by `notmuch-start-notmuch'."
- (let* ((err-file (process-get proc 'err-file))
- (err-buffer (or (process-get proc 'err-buffer)
- (find-file-noselect err-file)))
- (err (and (not (zerop (buffer-size err-buffer)))
+ (let* ((err-buffer (process-get proc 'err-buffer))
+ (err (and (buffer-live-p err-buffer)
+ (not (zerop (buffer-size err-buffer)))
(with-current-buffer err-buffer (buffer-string))))
- (sub-sentinel (process-get proc 'sub-sentinel))
- (real-command (process-get proc 'real-command)))
+ (sub-sentinel (process-get proc 'sub-sentinel)))
(condition-case err
(progn
;; Invoke the sub-sentinel, if any
;; and there's no point in telling the user that (but we
;; still check for and report stderr output below).
(when (buffer-live-p (process-buffer proc))
- (notmuch-check-async-exit-status proc event real-command err))
+ (notmuch-check-async-exit-status proc event nil err))
;; If that didn't signal an error, then any error output was
;; really warning output. Show warnings, if any.
(let ((warnings
(error
;; Emacs behaves strangely if an error escapes from a sentinel,
;; so turn errors into messages.
- (message "%s" (error-message-string err))))
- (when err-file (ignore-errors (delete-file err-file)))))
-
-(defun notmuch-start-notmuch-error-sentinel (proc event)
- (let* ((err-file (process-get proc 'err-file))
- ;; When `make-process' is available, use the error buffer
- ;; associated with the process, otherwise the error file.
- (err-buffer (or (process-get proc 'err-buffer)
- (find-file-noselect err-file))))
- (when err-buffer (kill-buffer err-buffer))))
-
-;; This variable is used only buffer local, but it needs to be
-;; declared globally first to avoid compiler warnings.
-(defvar notmuch-show-process-crypto nil)
-(make-variable-buffer-local 'notmuch-show-process-crypto)
+ (message "%s" (error-message-string err))))))
+
+(defun notmuch-start-notmuch-error-sentinel (proc _event)
+ (unless (process-live-p proc)
+ (let ((buffer (process-buffer proc)))
+ (when (buffer-live-p buffer)
+ (kill-buffer buffer)))))
+
+(defvar-local notmuch-show-process-crypto nil)
+
+;;; Generic Utilities
(defun notmuch-interactive-region ()
"Return the bounds of the current interactive region.
'notmuch-interactive-region
"notmuch 0.29")
+;;; _
+
(provide 'notmuch-lib)
;;; notmuch-lib.el ends here
-;;; notmuch-maildir-fcc.el --- inserting using a fcc handler
+;;; notmuch-maildir-fcc.el --- inserting using a fcc handler -*- lexical-binding: t -*-
;; Copyright © Jesse Rosenthal
;;
;;; Code:
-(eval-when-compile (require 'cl-lib))
+(require 'seq)
(require 'message)
(defvar notmuch-maildir-fcc-count 0)
+;;; Options
+
(defcustom notmuch-fcc-dirs "sent"
"Determines the Fcc Header which says where to save outgoing mail.
:require 'notmuch-fcc-initialization
:group 'notmuch-send)
-(defcustom notmuch-maildir-use-notmuch-insert 't
+(defcustom notmuch-maildir-use-notmuch-insert t
"Should fcc use notmuch insert instead of simple fcc."
:type '(choice :tag "Fcc Method"
(const :tag "Use notmuch insert" t)
(const :tag "Use simple fcc" nil))
:group 'notmuch-send)
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Functions which set up the fcc header in the message buffer.
+;;; Functions which set up the fcc header in the message buffer.
(defun notmuch-fcc-header-setup ()
"Add an Fcc header to the current message buffer.
-Sets the Fcc header based on the values of `notmuch-fcc-dirs'.
-
-Originally intended to be use a hook function, but now called directly
-by notmuch-mua-mail."
+If the Fcc header is already set, then keep it as-is.
+Otherwise set it according to `notmuch-fcc-dirs'."
(let ((subdir
(cond
((or (not notmuch-fcc-dirs)
;; Old style - no longer works.
(error "Invalid `notmuch-fcc-dirs' setting (old style)"))
((listp notmuch-fcc-dirs)
- (let* ((from (message-field-value "From"))
- (match
- (catch 'first-match
- (dolist (re-folder notmuch-fcc-dirs)
- (when (string-match-p (car re-folder) from)
- (throw 'first-match re-folder))))))
- (if match
- (cdr match)
- (message "No Fcc header added.")
- nil)))
+ (or (seq-some (let ((from (message-field-value "From")))
+ (pcase-lambda (`(,regexp . ,folder))
+ (and (string-match-p regexp from)
+ folder)))
+ notmuch-fcc-dirs)
+ (progn (message "No Fcc header added.")
+ nil)))
(t
(error "Invalid `notmuch-fcc-dirs' setting (neither string nor list)")))))
(when subdir
;; Notmuch insert does not accept absolute paths, so check the user
;; really want this header inserted.
(when (or (not (= (elt subdir 0) ?/))
- (y-or-n-p
- (format "Fcc header %s is an absolute path and notmuch insert is requested.
-Insert header anyway? " subdir)))
+ (y-or-n-p (format "Fcc header %s is an absolute path %s %s" subdir
+ "and notmuch insert is requested."
+ "Insert header anyway? ")))
(message-add-header (concat "Fcc: " subdir))))
(defun notmuch-maildir-add-file-style-fcc-header (subdir)
subdir
(concat (notmuch-database-path) "/" subdir))))))
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Functions for saving a message either using notmuch insert or file
-;; fcc. First functions common to the two cases.
+;;; Functions for saving a message using either method.
(defmacro with-temporary-notmuch-message-buffer (&rest body)
"Set-up a temporary copy of the current message-mode buffer."
,@body)))
(defun notmuch-maildir-setup-message-for-saving ()
- "Setup message for saving. Should be called on a temporary copy.
+ "Setup message for saving.
+This should be called on a temporary copy.
This is taken from the function message-do-fcc."
(message-encode-message-body)
(save-restriction
"Process Fcc headers in the current buffer.
This is a rearranged version of message mode's message-do-fcc."
- (let (list file)
+ (let (files file)
(save-excursion
(save-restriction
(message-narrow-to-headers)
(save-restriction
(message-narrow-to-headers)
(while (setq file (message-fetch-field "fcc" t))
- (push file list)
+ (push file files)
(message-remove-header "fcc" nil t)))
(notmuch-maildir-setup-message-for-saving)
;; Process FCC operations.
- (while list
- (setq file (pop list))
- (notmuch-fcc-handler file))
+ (mapc #'notmuch-fcc-handler files)
(kill-buffer (current-buffer)))))))
(defun notmuch-fcc-handler (fcc-header)
"Store message with notmuch insert or normal (file) fcc.
-If `notmuch-maildir-use-notmuch-insert` is set then store the
+If `notmuch-maildir-use-notmuch-insert' is set then store the
message using notmuch insert. Otherwise store the message using
normal fcc."
(message "Doing Fcc...")
(if notmuch-maildir-use-notmuch-insert
(notmuch-maildir-fcc-with-notmuch-insert fcc-header)
- (notmuch-maildir-fcc-file-fcc fcc-header)))
+ (notmuch-maildir-fcc-file-fcc fcc-header))
+ (message "Doing Fcc...done"))
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Functions for saving a message using notmuch insert.
+;;; Functions for saving a message using notmuch insert.
(defun notmuch-maildir-notmuch-insert-current-buffer (folder &optional create tags)
"Use notmuch insert to put the current buffer in the database.
database in folder FOLDER. If CREATE is non-nil it will supply
the --create-folder flag to create the folder if necessary. TAGS
should be a list of tag changes to apply to the inserted message."
- (let* ((args (append (and create (list "--create-folder"))
- (list (concat "--folder=" folder))
- tags)))
- (apply 'notmuch-call-notmuch-process
- :stdin-string (buffer-string) "insert" args)))
+ (apply 'notmuch-call-notmuch-process
+ :stdin-string (buffer-string) "insert"
+ (append (and create (list "--create-folder"))
+ (list (concat "--folder=" folder))
+ tags)))
(defun notmuch-maildir-fcc-with-notmuch-insert (fcc-header &optional create)
"Store message with notmuch insert.
or surrounding the entire folder name in double quotes.
If CREATE is non-nil then create the folder if necessary."
- (let* ((args (split-string-and-unquote fcc-header))
- (folder (car args))
- (tags (cdr args)))
+ (pcase-let ((`(,folder . ,tags)
+ (split-string-and-unquote fcc-header)))
(condition-case nil
(notmuch-maildir-notmuch-insert-current-buffer folder create tags)
;; Since there are many reasons notmuch insert could fail, e.g.,
\(r)etry, (c)reate folder, (i)gnore, or (e)dit the header? " '(?r ?c ?i ?e))))
(cl-case response
(?r (notmuch-maildir-fcc-with-notmuch-insert fcc-header))
- (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header 't))
- (?i 't)
+ (?c (notmuch-maildir-fcc-with-notmuch-insert fcc-header t))
+ (?i t)
(?e (notmuch-maildir-fcc-with-notmuch-insert
(read-from-minibuffer "Fcc header: " fcc-header)))))))))
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Functions for saving a message using file fcc.
+;;; Functions for saving a message using file fcc.
(defun notmuch-maildir-fcc-host-fixer (hostname)
(replace-regexp-in-string "/\\|:"
(let* ((ftime (float-time))
(microseconds (mod (* 1000000 ftime) 1000000))
(hostname (notmuch-maildir-fcc-host-fixer (system-name))))
- (setq notmuch-maildir-fcc-count (+ notmuch-maildir-fcc-count 1))
+ (cl-incf notmuch-maildir-fcc-count)
(format "%d.%d_%d_%d.%s"
ftime
(emacs-pid)
(write-file (concat destdir "/tmp/" msg-id))
msg-id)
(t
- (error (format "Can't write to %s. Not a maildir."
- destdir))
- nil))))
+ (error "Can't write to %s. Not a maildir." destdir)))))
(defun notmuch-maildir-fcc-move-tmp-to-new (destdir msg-id)
(add-name-to-file
(defun notmuch-maildir-fcc-file-fcc (fcc-header)
"Write the message to the file specified by FCC-HEADER.
-It offers the user a chance to correct the header, or filesystem,
-if needed."
+If that fails, then offer the user a chance to correct the header
+or filesystem."
(if (notmuch-maildir-fcc-dir-is-maildir-p fcc-header)
- (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header 't)
+ (notmuch-maildir-fcc-write-buffer-to-maildir fcc-header t)
;; The fcc-header is not a valid maildir see if the user wants to
;; fix it in some way.
(let* ((prompt (format "Fcc %s is not a maildir: \
(message "No permission to create %s." fcc-header)
(sit-for 2))
(notmuch-maildir-fcc-file-fcc fcc-header))
- (?i 't)
+ (?i t)
(?e (notmuch-maildir-fcc-file-fcc
(read-from-minibuffer "Fcc header: " fcc-header)))))))
(defun notmuch-maildir-fcc-write-buffer-to-maildir (destdir &optional mark-seen)
- "Writes the current buffer to maildir destdir. If mark-seen is
-non-nil, it will write it to cur/, and mark it as read. It should
-return t if successful, and nil otherwise."
+ "Write the current buffer to maildir destdir.
+
+If mark-seen is non-nil, then write it to \"cur/\", and mark it
+as read, otherwise write it to \"new/\". Return t if successful,
+and nil otherwise."
(let ((orig-buffer (buffer-name)))
(with-temp-buffer
(insert-buffer-substring orig-buffer)
(catch 'link-error
(let ((msg-id (notmuch-maildir-fcc-save-buffer-to-tmp destdir)))
(when msg-id
- (cond (mark-seen
- (condition-case err
- (notmuch-maildir-fcc-move-tmp-to-cur destdir msg-id t)
- (file-already-exists
- (throw 'link-error nil))))
- (t
- (condition-case err
- (notmuch-maildir-fcc-move-tmp-to-new destdir msg-id)
- (file-already-exists
- (throw 'link-error nil))))))
+ (condition-case nil
+ (if mark-seen
+ (notmuch-maildir-fcc-move-tmp-to-cur destdir msg-id t)
+ (notmuch-maildir-fcc-move-tmp-to-new destdir msg-id))
+ (file-already-exists
+ (throw 'link-error nil))))
(delete-file (concat destdir "/tmp/" msg-id))))
t)))
+;;; _
+
(provide 'notmuch-maildir-fcc)
;;; notmuch-maildir-fcc.el ends here
-;;; notmuch-message.el --- message-mode functions specific to notmuch
+;;; notmuch-message.el --- message-mode functions specific to notmuch -*- lexical-binding: t -*-
;;
;; Copyright © Jesse Rosenthal
;;
;;; Code:
+(require 'cl-lib)
+(require 'pcase)
+(require 'subr-x)
+
(require 'message)
(require 'notmuch-tag)
:type '(repeat string)
:group 'notmuch-send)
-(defconst notmuch-message-queued-tag-changes nil
- "List of messages and corresponding tag-changes to be applied when sending a message.
+(defvar-local notmuch-message-queued-tag-changes nil
+ "List of tag changes to be applied when sending a message.
-This variable is overridden by buffer-local versions in message
-buffers where tag changes should be triggered when sending off
-the message. Each item in this list is a list of strings, where
-the first is a notmuch query and the rest are the tag changes to
-be applied to the matching messages.")
+A list of queries and tag changes that are to be applied to them
+when the message that was composed in the current buffer is being
+send. Each item in this list is a list of strings, where the
+first is a notmuch query and the rest are the tag changes to be
+applied to the matching messages.")
(defun notmuch-message-apply-queued-tag-changes ()
;; Apply the tag changes queued in the buffer-local variable
;; notmuch-message-queued-tag-changes.
- (dolist (query-and-tags notmuch-message-queued-tag-changes)
- (notmuch-tag (car query-and-tags)
- (cdr query-and-tags))))
+ (pcase-dolist (`(,query . ,tags) notmuch-message-queued-tag-changes)
+ (notmuch-tag query tags)))
(add-hook 'message-send-hook 'notmuch-message-apply-queued-tag-changes)
-;;; notmuch-mua.el --- emacs style mail-user-agent
+;;; notmuch-mua.el --- emacs style mail-user-agent -*- lexical-binding: t -*-
;;
;; Copyright © David Edmondson
;;
;;; Code:
-(eval-when-compile (require 'cl-lib))
-
(require 'message)
(require 'mm-view)
(require 'format-spec)
(declare-function notmuch-draft-postpone "notmuch-draft" ())
(declare-function notmuch-draft-save "notmuch-draft" ())
-;;
+(defvar notmuch-show-indent-multipart)
+(defvar notmuch-show-insert-header-p-function)
+(defvar notmuch-show-max-text-part-size)
+(defvar notmuch-show-insert-text/plain-hook)
+
+;;; Options
(defcustom notmuch-mua-send-hook nil
"Hook run before sending messages."
:group 'notmuch-hooks)
(defcustom notmuch-mua-compose-in 'current-window
- (concat
- "Where to create the mail buffer used to compose a new message.
+ "Where to create the mail buffer used to compose a new message.
Possible values are `current-window' (default), `new-window' and
`new-frame'. If set to `current-window', the mail buffer will be
displayed in the current window, so the old buffer will be
window/frame that will be destroyed when the buffer is killed.
You may want to customize `message-kill-buffer-on-exit'
accordingly."
- (when (< emacs-major-version 24)
- " Due to a known bug in Emacs 23, you should not set
-this to `new-window' if `message-kill-buffer-on-exit' is
-disabled: this would result in an incorrect behavior."))
:group 'notmuch-send
:type '(choice (const :tag "Compose in the current window" current-window)
(const :tag "Compose mail in a new window" new-window)
(const :tag "Compose mail in a new frame" new-frame)))
(defcustom notmuch-mua-user-agent-function nil
- "Function used to generate a `User-Agent:' string. If this is
-`nil' then no `User-Agent:' will be generated."
+ "Function used to generate a `User-Agent:' string.
+If this is `nil' then no `User-Agent:' will be generated."
:type '(choice (const :tag "No user agent string" nil)
(const :tag "Full" notmuch-mua-user-agent-full)
(const :tag "Notmuch" notmuch-mua-user-agent-notmuch)
:group 'notmuch-send)
(defcustom notmuch-mua-hidden-headers nil
- "Headers that are added to the `message-mode' hidden headers
-list."
+ "Headers that are added to the `message-mode' hidden headers list."
+ :type '(repeat string)
+ :group 'notmuch-send)
+
+(defcustom notmuch-identities nil
+ "Identities that can be used as the From: address when composing a new message.
+
+If this variable is left unset, then a list will be constructed from the
+name and addresses configured in the notmuch configuration file."
:type '(repeat string)
:group 'notmuch-send)
+(defcustom notmuch-always-prompt-for-sender nil
+ "Always prompt for the From: address when composing or forwarding a message.
+
+This is not taken into account when replying to a message, because in that case
+the From: header is already filled in by notmuch."
+ :type 'boolean
+ :group 'notmuch-send)
+
(defgroup notmuch-reply nil
- "Replying to messages in notmuch"
+ "Replying to messages in notmuch."
:group 'notmuch)
(defcustom notmuch-mua-cite-function 'message-cite-original
- "*Function for citing an original message.
+ "Function for citing an original message.
+
Predefined functions include `message-cite-original' and
-`message-cite-original-without-signature'.
-Note that these functions use `mail-citation-hook' if that is non-nil."
+`message-cite-original-without-signature'. Note that these
+functions use `mail-citation-hook' if that is non-nil."
:type '(radio (function-item message-cite-original)
(function-item message-cite-original-without-signature)
(function-item sc-cite-original)
:type 'regexp
:group 'notmuch-send)
-;;
+;;; Various functions
(defun notmuch-mua-attachment-check ()
- "Signal an error if the message text indicates that an
-attachment is expected but no MML referencing an attachment is
-found.
+ "Signal an error an attachement is expected but missing.
+
+Signal an error if the message text indicates that an attachment
+is expected but no MML referencing an attachment is found.
Typically this is added to `notmuch-mua-send-hook'."
(when (and
(defun notmuch-mua-get-switch-function ()
"Get a switch function according to `notmuch-mua-compose-in'."
- (cond ((eq notmuch-mua-compose-in 'current-window)
- 'switch-to-buffer)
- ((eq notmuch-mua-compose-in 'new-window)
- 'switch-to-buffer-other-window)
- ((eq notmuch-mua-compose-in 'new-frame)
- 'switch-to-buffer-other-frame)
- (t (error "Invalid value for `notmuch-mua-compose-in'"))))
+ (pcase notmuch-mua-compose-in
+ ('current-window 'switch-to-buffer)
+ ('new-window 'switch-to-buffer-other-window)
+ ('new-frame 'switch-to-buffer-other-frame)
+ (_ (error "Invalid value for `notmuch-mua-compose-in'"))))
(defun notmuch-mua-maybe-set-window-dedicated ()
- "Set the selected window as dedicated according to
-`notmuch-mua-compose-in'."
+ "Set the selected window as dedicated according to `notmuch-mua-compose-in'."
(when (or (eq notmuch-mua-compose-in 'new-frame)
(eq notmuch-mua-compose-in 'new-window))
(set-window-dedicated-p (selected-window) t)))
(defun notmuch-mua-reply-crypto (parts)
"Add mml sign-encrypt flag if any part of original message is encrypted."
(cl-loop for part in parts
- if (notmuch-match-content-type (plist-get part :content-type)
- "multipart/encrypted")
+ for type = (plist-get part :content-type)
+ if (notmuch-match-content-type type "multipart/encrypted")
do (mml-secure-message-sign-encrypt)
- else if (notmuch-match-content-type (plist-get part :content-type)
- "multipart/*")
+ else if (notmuch-match-content-type type "multipart/*")
do (notmuch-mua-reply-crypto (plist-get part :content))))
-;; There is a bug in emacs 23's message.el that results in a newline
+;; There is a bug in Emacs' message.el that results in a newline
;; not being inserted after the References header, so the next header
;; is concatenated to the end of it. This function fixes the problem,
;; while guarding against the possibility that some current or future
(funcall original-func header references)
(unless (bolp) (insert "\n")))
+;;; Mua reply
+
(defun notmuch-mua-reply (query-string &optional sender reply-all)
(let ((args '("reply" "--format=sexp" "--format-version=4"))
(process-crypto notmuch-show-process-crypto)
;; Create a buffer-local queue for tag changes triggered when
;; sending the reply.
(when notmuch-message-replied-tags
- (setq-local notmuch-message-queued-tag-changes
- (list (cons query-string notmuch-message-replied-tags))))
+ (setq notmuch-message-queued-tag-changes
+ (list (cons query-string notmuch-message-replied-tags))))
;; Insert the message body - but put it in front of the signature
;; if one is present, and after any other content
;; message*setup-hooks may have added to the message body already.
(message-goto-body)
(set-buffer-modified-p nil))
+;;; Mode and keymap
+
+(defvar notmuch-message-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
+ (define-key map (kbd "C-c C-s") #'notmuch-mua-send)
+ (define-key map (kbd "C-c C-p") #'notmuch-draft-postpone)
+ (define-key map (kbd "C-x C-s") #'notmuch-draft-save)
+ map)
+ "Keymap for `notmuch-message-mode'.")
+
(define-derived-mode notmuch-message-mode message-mode "Message[Notmuch]"
"Notmuch message composition mode. Mostly like `message-mode'."
(notmuch-address-setup))
(put 'notmuch-message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
-(define-key notmuch-message-mode-map (kbd "C-c C-c") #'notmuch-mua-send-and-exit)
-(define-key notmuch-message-mode-map (kbd "C-c C-s") #'notmuch-mua-send)
-(define-key notmuch-message-mode-map (kbd "C-c C-p") #'notmuch-draft-postpone)
-(define-key notmuch-message-mode-map (kbd "C-x C-s") #'notmuch-draft-save)
+;;; New messages
(defun notmuch-mua-pop-to-buffer (name switch-function)
- "Pop to buffer NAME, and warn if it already exists and is
-modified. This function is notmuch adaptation of
-`message-pop-to-buffer'."
+ "Pop to buffer NAME, and warn if it already exists and is modified.
+Like `message-pop-to-buffer' but enable `notmuch-message-mode'
+instead of `message-mode' and SWITCH-FUNCTION is mandatory."
(let ((buffer (get-buffer name)))
(if (and buffer
(buffer-name buffer))
(select-window window))
(funcall switch-function buffer)
(set-buffer buffer))
- (when (and (buffer-modified-p)
- (not (prog1
- (y-or-n-p
- "Message already being composed; erase? ")
- (message nil))))
- (error "Message being composed")))
+ (when (buffer-modified-p)
+ (if (y-or-n-p "Message already being composed; erase? ")
+ (message nil)
+ (error "Message being composed"))))
(funcall switch-function name)
(set-buffer name))
(erase-buffer)
(notmuch-message-mode)))
-(defun notmuch-mua-mail (&optional to subject other-headers continue
+(defun notmuch-mua-mail (&optional to subject other-headers _continue
switch-function yank-action send-actions
return-action &rest ignored)
"Invoke the notmuch mail composition window."
(interactive)
(when notmuch-mua-user-agent-function
(let ((user-agent (funcall notmuch-mua-user-agent-function)))
- (unless (string= "" user-agent)
+ (unless (string-empty-p user-agent)
(push (cons 'User-Agent user-agent) other-headers))))
(unless (assq 'From other-headers)
(push (cons 'From (message-make-from
(dolist (h other-headers other-headers)
(when (stringp (car h))
(setcar h (intern (capitalize (car h))))))))
- (args (list yank-action send-actions))
;; Cause `message-setup-1' to do things relevant for mail,
;; such as observe `message-default-mail-headers'.
(message-this-is-mail t))
- ;; message-setup-1 in Emacs 23 does not accept return-action
- ;; argument. Pass it only if it is supplied by the caller. This
- ;; will never be the case when we're called by `compose-mail' in
- ;; Emacs 23.
- (when return-action (nconc args '(return-action)))
- (apply 'message-setup-1 headers args))
+ (message-setup-1 headers yank-action send-actions return-action))
(notmuch-fcc-header-setup)
(message-sort-headers)
(message-hide-headers)
(notmuch-mua-maybe-set-window-dedicated)
(message-goto-to))
-(defcustom notmuch-identities nil
- "Identities that can be used as the From: address when composing a new message.
-
-If this variable is left unset, then a list will be constructed from the
-name and addresses configured in the notmuch configuration file."
- :type '(repeat string)
- :group 'notmuch-send)
-
-(defcustom notmuch-always-prompt-for-sender nil
- "Always prompt for the From: address when composing or forwarding a message.
-
-This is not taken into account when replying to a message, because in that case
-the From: header is already filled in by notmuch."
- :type 'boolean
- :group 'notmuch-send)
-
(defvar notmuch-mua-sender-history nil)
(defun notmuch-mua-prompt-for-sender ()
"Prompt for a sender from the user's configured identities."
(if notmuch-identities
- (ido-completing-read "Send mail from: " notmuch-identities
- nil nil nil 'notmuch-mua-sender-history
- (car notmuch-identities))
+ (completing-read "Send mail from: " notmuch-identities
+ nil nil nil 'notmuch-mua-sender-history
+ (car notmuch-identities))
(let* ((name (notmuch-user-name))
(addrs (cons (notmuch-user-primary-email)
(notmuch-user-other-email)))
(address
- (ido-completing-read (concat "Sender address for " name ": ") addrs
- nil nil nil 'notmuch-mua-sender-history
- (car addrs))))
+ (completing-read (concat "Sender address for " name ": ") addrs
+ nil nil nil 'notmuch-mua-sender-history
+ (car addrs))))
(message-make-from name address))))
(put 'notmuch-mua-new-mail 'notmuch-prefix-doc "... and prompt for sender")
;; Create a buffer-local queue for tag changes triggered when
;; sending the message.
(when notmuch-message-forwarded-tags
- (setq-local notmuch-message-queued-tag-changes
- (cl-loop for id in forward-queries
- collect
- (cons id notmuch-message-forwarded-tags))))
+ (setq notmuch-message-queued-tag-changes
+ (cl-loop for id in forward-queries
+ collect
+ (cons id notmuch-message-forwarded-tags))))
;; `message-forward-make-body' shows the User-agent header. Hide
;; it again.
(message-hide-headers)
If PROMPT-FOR-SENDER is non-nil, the user will be prompted for
the From: address first. If REPLY-ALL is non-nil, the message
will be addressed to all recipients of the source message."
- ;; In current emacs (24.3) select-active-regions is set to t by
- ;; default. The reply insertion code sets the region to the quoted
- ;; message to make it easy to delete (kill-region or C-w). These two
- ;; things combine to put the quoted message in the primary selection.
+ ;; `select-active-regions' is t by default. The reply insertion code
+ ;; sets the region to the quoted message to make it easy to delete
+ ;; (kill-region or C-w). These two things combine to put the quoted
+ ;; message in the primary selection.
;;
;; This is not what the user wanted and is a privacy risk (accidental
;; pasting of the quoted message). We can avoid some of the problems
(notmuch-mua-reply query-string sender reply-all)
(deactivate-mark)))
+;;; Checks
+
(defun notmuch-mua-check-no-misplaced-secure-tag ()
"Query user if there is a misplaced secure mml tag.
(goto-char (point-max))
(or
;; We are always fine if there is no secure tag.
- (not (search-backward "<#secure" nil 't))
+ (not (search-backward "<#secure" nil t))
;; There is a secure tag, so it must be at the start of the
;; body, with no secure tag earlier (i.e., in the headers).
(and (= (point) body-start)
- (not (search-backward "<#secure" nil 't)))
+ (not (search-backward "<#secure" nil t)))
;; The user confirms they means it.
(yes-or-no-p "\
There is a <#secure> tag not at the start of the body. It is
newline. It is likely that the message will be sent unsigned and
unencrypted. Really send? "))))
+;;; Finishing commands
+
(defun notmuch-mua-send-common (arg &optional exit)
(interactive "P")
(run-hooks 'notmuch-mua-send-hook)
(defun notmuch-mua-send-and-exit (&optional arg)
(interactive "P")
- (notmuch-mua-send-common arg 't))
+ (notmuch-mua-send-common arg t))
(defun notmuch-mua-send (&optional arg)
(interactive "P")
(interactive)
(message-kill-buffer))
-;;
+;;; _
(define-mail-user-agent 'notmuch-user-agent
- 'notmuch-mua-mail 'notmuch-mua-send-and-exit
- 'notmuch-mua-kill-buffer 'notmuch-mua-send-hook)
+ 'notmuch-mua-mail
+ 'notmuch-mua-send-and-exit
+ 'notmuch-mua-kill-buffer
+ 'notmuch-mua-send-hook)
;; Add some more headers to the list that `message-mode' hides when
;; composing a message.
(notmuch-mua-add-more-hidden-headers)
-;;
-
(provide 'notmuch-mua)
;;; notmuch-mua.el ends here
-;;; notmuch-parser.el --- streaming S-expression parser
+;;; notmuch-parser.el --- streaming S-expression parser -*- lexical-binding: t -*-
;;
;; Copyright © Austin Clements
;;
;;; Code:
-(eval-when-compile (require 'cl-lib))
+(require 'cl-lib)
+(require 'pcase)
+(require 'subr-x)
(defun notmuch-sexp-create-parser ()
"Return a new streaming S-expression parser.
(forward-char)
(signal 'invalid-read-syntax (list (string (char-before)))))))
-(defun notmuch-sexp-eof (sp)
- "Signal an error if there is more data in SP's buffer.
-
-Moves point to the beginning of any trailing data or to the end
-of the buffer if there is only trailing whitespace."
- (skip-chars-forward " \n\r\t")
- (unless (eobp)
- (error "Trailing garbage following expression")))
-
(defvar notmuch-sexp--parser nil
"The buffer-local notmuch-sexp-parser instance.
move point in the input buffer."
;; Set up the initial state
(unless (local-variable-p 'notmuch-sexp--parser)
- (set (make-local-variable 'notmuch-sexp--parser)
- (notmuch-sexp-create-parser))
- (set (make-local-variable 'notmuch-sexp--state) 'begin))
+ (setq-local notmuch-sexp--parser (notmuch-sexp-create-parser))
+ (setq-local notmuch-sexp--state 'begin))
(let (done)
(while (not done)
(cl-case notmuch-sexp--state
(t (with-current-buffer result-buffer
(funcall result-function result))))))
(end
- ;; Any trailing data is unexpected
- (notmuch-sexp-eof notmuch-sexp--parser)
+ ;; Skip over trailing whitespace.
+ (skip-chars-forward " \n\r\t")
+ ;; Any trailing data is unexpected.
+ (unless (eobp)
+ (error "Trailing garbage following expression"))
(setq done t)))))
;; Clear out what we've parsed
(delete-region (point-min) (point)))
-;;; notmuch-print.el --- printing messages from notmuch
+;;; notmuch-print.el --- printing messages from notmuch -*- lexical-binding: t -*-
;;
;; Copyright © David Edmondson
;;
(declare-function notmuch-show-get-prop "notmuch-show" (prop &optional props))
+;;; Options
+
(defcustom notmuch-print-mechanism 'notmuch-print-lpr
"How should printing be done?"
:group 'notmuch-show
(function :tag "Use muttprint then evince" notmuch-print-muttprint/evince)
(function :tag "Using a custom function")))
-;; Utility functions:
+;;; Utility functions
(defun notmuch-print-run-evince (file)
"View FILE using 'evince'."
"--printed-headers" "Date_To_From_CC_Newsgroups_*Subject*_/Tags/"
output))
-;; User-visible functions:
+;;; User-visible functions
-(defun notmuch-print-lpr (msg)
+(defun notmuch-print-lpr (_msg)
"Print a message buffer using lpr."
(lpr-buffer))
(ps-print-buffer ps-file)
(notmuch-print-run-evince ps-file)))
-(defun notmuch-print-muttprint (msg)
+(defun notmuch-print-muttprint (_msg)
"Print a message using muttprint."
(notmuch-print-run-muttprint))
-(defun notmuch-print-muttprint/evince (msg)
+(defun notmuch-print-muttprint/evince (_msg)
"Preview a message buffer using muttprint and evince."
(let ((ps-file (make-temp-file "notmuch" nil ".ps")))
(notmuch-print-run-muttprint (list "--printer" (concat "TO_FILE:" ps-file)))
(set-buffer-modified-p nil)
(funcall notmuch-print-mechanism msg))
+;;; _
+
(provide 'notmuch-print)
;;; notmuch-print.el ends here
-;;; notmuch-query.el --- provide an emacs api to query notmuch
+;;; notmuch-query.el --- provide an emacs api to query notmuch -*- lexical-binding: t -*-
;;
;; Copyright © David Bremner
;;
(require 'notmuch-lib)
+;;; Basic query function
+
(defun notmuch-query-get-threads (search-terms)
"Return a list of threads of messages matching SEARCH-TERMS.
(setq args (append args search-terms))
(apply #'notmuch-call-notmuch-sexp args)))
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Mapping functions across collections of messages.
+;;; Mapping functions across collections of messages
(defun notmuch-query-map-aux (mapper function seq)
"Private function to do the actual mapping and flattening."
- (apply 'append
- (mapcar
- (lambda (tree)
- (funcall mapper function tree))
- seq)))
+ (cl-mapcan (lambda (tree)
+ (funcall mapper function tree))
+ seq))
(defun notmuch-query-map-threads (fn threads)
"Apply function FN to every thread in THREADS.
"Apply function FN to every message in TREE.
Flatten results to a list. See the function
`notmuch-query-get-threads' for more information."
- (cons (funcall fn (car tree)) (notmuch-query-map-forest fn (cadr tree))))
+ (cons (funcall fn (car tree))
+ (notmuch-query-map-forest fn (cadr tree))))
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Predefined queries
+;;; Predefined queries
(defun notmuch-query-get-message-ids (&rest search-terms)
"Return a list of message-ids of messages that match SEARCH-TERMS."
-;;; notmuch-show.el --- displaying notmuch forests
+;;; notmuch-show.el --- displaying notmuch forests -*- lexical-binding: t -*-
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
;;; Code:
-(eval-when-compile
- (require 'cl-lib)
- (require 'pcase))
-
(require 'mm-view)
(require 'message)
(require 'mm-decode)
(declare-function notmuch-read-query "notmuch" (prompt))
(declare-function notmuch-draft-resume "notmuch-draft" (id))
+(defvar shr-blocked-images)
+(defvar gnus-blocked-images)
+(defvar shr-content-function)
+
+;;; Options
+
(defcustom notmuch-message-headers '("Subject" "To" "Cc" "Date")
"Headers that should be shown in a message, in this order.
:type '(choice (const nil) regexp)
:group 'notmuch-show)
-(defvar notmuch-show-thread-id nil)
-(make-variable-buffer-local 'notmuch-show-thread-id)
+;;; Variables
+
+(defvar-local notmuch-show-thread-id nil)
-(defvar notmuch-show-parent-buffer nil)
-(make-variable-buffer-local 'notmuch-show-parent-buffer)
+(defvar-local notmuch-show-parent-buffer nil)
-(defvar notmuch-show-query-context nil)
-(make-variable-buffer-local 'notmuch-show-query-context)
+(defvar-local notmuch-show-query-context nil)
-(defvar notmuch-show-process-crypto nil)
-(make-variable-buffer-local 'notmuch-show-process-crypto)
+(defvar-local notmuch-show-process-crypto nil)
-(defvar notmuch-show-elide-non-matching-messages nil)
-(make-variable-buffer-local 'notmuch-show-elide-non-matching-messages)
+(defvar-local notmuch-show-elide-non-matching-messages nil)
-(defvar notmuch-show-indent-content t)
-(make-variable-buffer-local 'notmuch-show-indent-content)
+(defvar-local notmuch-show-indent-content t)
(defvar notmuch-show-attachment-debug nil
"If t log stdout and stderr from attachment handlers.
When set to nil (the default) stdout and stderr from attachment
handlers is discarded. When set to t the stdout and stderr from
each attachment handler is logged in buffers with names beginning
-\" *notmuch-part*\". This option requires emacs version at least
-24.3 to work.")
+\" *notmuch-part*\".")
+
+;;; Options
(defcustom notmuch-show-stash-mlarchive-link-alist
'(("Gmane" . "https://mid.gmane.org/")
:type 'boolean
:group 'notmuch-show)
+;;; Utilities
+
(defmacro with-current-notmuch-show-message (&rest body)
"Evaluate body with current buffer set to the text of current message."
`(save-excursion
"Enable Visual Line mode."
(visual-line-mode t))
+;;; Commands
+
;; DEPRECATED in Notmuch 0.16 since we now have convenient part
;; commands. We'll keep the command around for a version or two in
;; case people want to bind it themselves.
(header (concat
"Subject: " subject "\n"
"To: " to "\n"
- (if (not (string= cc ""))
+ (if (not (string-empty-p cc))
(concat "Cc: " cc "\n")
"")
"From: " from "\n"
(interactive)
(notmuch-show-with-message-as-text 'notmuch-print-message))
+;;; Headers
+
(defun notmuch-show-fontify-header ()
(let ((face (cond
((looking-at "[Tt]o:")
(narrow-to-region start (point-max))
(run-hooks 'notmuch-show-markup-headers-hook)))))
+;;; Parts
+
(define-button-type 'notmuch-show-part-button-type
'action 'notmuch-show-part-button-default
'follow-link t
'face 'message-mml
:supertype 'notmuch-button-type)
-(defun notmuch-show-insert-part-header (nth content-type declared-type
+(defun notmuch-show-insert-part-header (_nth content-type declared-type
&optional name comment)
(let ((base-label (concat (and name (concat name ": "))
declared-type
(overlay-put overlay 'invisible (not show))
t)))))))
-;; Part content ID handling
+;;; Part content ID handling
(defvar notmuch-show--cids nil
"Alist from raw content ID to (MSG PART).")
;; alternative (even if we can't render it).
(push (list content-id msg part) notmuch-show--cids)))
;; Recurse on sub-parts
- (let ((ctype (notmuch-split-content-type
- (downcase (plist-get part :content-type)))))
- (cond ((equal (car ctype) "multipart")
- (mapc (apply-partially #'notmuch-show--register-cids msg)
- (plist-get part :content)))
- ((equal ctype '("message" "rfc822"))
- (notmuch-show--register-cids
- msg
- (car (plist-get (car (plist-get part :content)) :body)))))))
+ (when-let ((type (plist-get part :content-type)))
+ (pcase-let ((`(,type ,subtype)
+ (split-string (downcase type) "/")))
+ (cond ((equal type "multipart")
+ (mapc (apply-partially #'notmuch-show--register-cids msg)
+ (plist-get part :content)))
+ ((and (equal type "message")
+ (equal subtype "rfc822"))
+ (notmuch-show--register-cids
+ msg
+ (car (plist-get (car (plist-get part :content)) :body))))))))
(defun notmuch-show--get-cid-content (cid)
"Return a list (CID-content content-type) or nil.
into the current buffer. CID must be a raw content ID, without
enclosing angle brackets, a cid: prefix, or URL encoding. This
will return nil if the CID is unknown or cannot be retrieved."
- (let ((descriptor (cdr (assoc cid notmuch-show--cids))))
- (when descriptor
- (let* ((msg (car descriptor))
- (part (cadr descriptor))
- ;; Request caching for this content, as some messages
- ;; reference the same cid: part many times (hundreds!).
- (content (notmuch-get-bodypart-binary
- msg part notmuch-show-process-crypto 'cache))
- (content-type (plist-get part :content-type)))
- (list content content-type)))))
+ (when-let ((descriptor (cdr (assoc cid notmuch-show--cids))))
+ (pcase-let ((`(,msg ,part) descriptor))
+ ;; Request caching for this content, as some messages
+ ;; reference the same cid: part many times (hundreds!).
+ (list (notmuch-get-bodypart-binary
+ msg part notmuch-show-process-crypto 'cache)
+ (plist-get part :content-type)))))
(defun notmuch-show-setup-w3m ()
"Instruct w3m how to retrieve content from a \"related\" part of a message."
(setq mm-html-inhibit-images nil))
(defvar w3m-current-buffer) ;; From `w3m.el'.
-(defun notmuch-show--cid-w3m-retrieve (url &rest args)
+(defun notmuch-show--cid-w3m-retrieve (url &rest _args)
;; url includes the cid: prefix and is URL encoded (see RFC 2392).
(let* ((cid (url-unhex-string (substring url 4)))
(content-and-type
(mapcar (lambda (inner-part) (plist-get inner-part :content-type))
(plist-get part :content)))
-(defun notmuch-show-insert-part-multipart/alternative (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/alternative (msg part _content-type _nth depth _button)
(let ((chosen-type (car (notmuch-multipart/alternative-choose
msg (notmuch-show-multipart/*-to-list part))))
(inner-parts (plist-get part :content))
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-multipart/related (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/related (msg part _content-type _nth depth _button)
(let ((inner-parts (plist-get part :content))
(start (point)))
;; Render the primary part. FIXME: Support RFC 2387 Start header.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-multipart/signed (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/signed (msg part _content-type _nth depth button)
(when button
(button-put button 'face 'notmuch-crypto-part-header))
;; Insert a button detailing the signature status.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-multipart/encrypted (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/encrypted (msg part _content-type _nth depth button)
(when button
(button-put button 'face 'notmuch-crypto-part-header))
;; Insert a button detailing the encryption status.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-application/pgp-encrypted (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-application/pgp-encrypted (_msg _part _content-type _nth _depth _button)
t)
-(defun notmuch-show-insert-part-multipart/* (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-multipart/* (msg part _content-type _nth depth _button)
(let ((inner-parts (plist-get part :content))
(start (point)))
;; Show all of the parts.
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-message/rfc822 (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-message/rfc822 (msg part _content-type _nth depth _button)
(let* ((message (car (plist-get part :content)))
(body (car (plist-get message :body)))
(start (point)))
(indent-rigidly start (point) 1)))
t)
-(defun notmuch-show-insert-part-text/plain (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-text/plain (msg part _content-type _nth depth button)
;; For backward compatibility we want to apply the text/plain hook
;; to the whole of the part including the part button if there is
;; one.
(run-hook-with-args 'notmuch-show-insert-text/plain-hook msg depth))))
t)
-(defun notmuch-show-insert-part-text/calendar (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-text/calendar (msg part _content-type _nth _depth _button)
(insert (with-temp-buffer
(insert (notmuch-get-bodypart-text msg part notmuch-show-process-crypto))
;; notmuch-get-bodypart-text does no newline conversion.
t)
;; For backwards compatibility.
-(defun notmuch-show-insert-part-text/x-vcalendar (msg part content-type nth depth button)
- (notmuch-show-insert-part-text/calendar msg part content-type nth depth button))
+(defun notmuch-show-insert-part-text/x-vcalendar (msg part _content-type _nth depth _button)
+ (notmuch-show-insert-part-text/calendar msg part nil nil depth nil))
(when (version< emacs-version "25.3")
;; https://bugs.gnu.org/28350
;; the first time).
(require 'enriched)
(cl-letf (((symbol-function 'enriched-decode-display-prop)
- (lambda (start end &optional param) (list start end))))
+ (lambda (start end &optional _param) (list start end))))
(notmuch-show-insert-part-*/* msg part content-type nth depth button))))
(defun notmuch-show-get-mime-type-of-application/octet-stream (part)
(gnus-blocked-images notmuch-show-text/html-blocked-images))
(notmuch-show-insert-part-*/* msg part content-type nth depth button))))
-;; These functions are used by notmuch-show--insert-part-text/html-shr
+;;; Functions used by notmuch-show--insert-part-text/html-shr
+
(declare-function libxml-parse-html-region "xml.c")
(declare-function shr-insert-document "shr")
(shr-insert-document dom)
t))
-(defun notmuch-show-insert-part-*/* (msg part content-type nth depth button)
+(defun notmuch-show-insert-part-*/* (msg part content-type _nth _depth _button)
;; This handler _must_ succeed - it is the handler of last resort.
(notmuch-mm-display-part-inline msg part content-type notmuch-show-process-crypto)
t)
-;; Functions for determining how to handle MIME parts.
+;;; Functions for determining how to handle MIME parts.
(defun notmuch-show-handlers-for (content-type)
"Return a list of content handlers for a part of type CONTENT-TYPE."
(push func result)))
;; Reverse order of prefrence.
(list (intern (concat "notmuch-show-insert-part-*/*"))
- (intern (concat
- "notmuch-show-insert-part-"
- (car (notmuch-split-content-type content-type))
- "/*"))
+ (intern (concat "notmuch-show-insert-part-"
+ (car (split-string content-type "/"))
+ "/*"))
(intern (concat "notmuch-show-insert-part-" content-type))))
result))
-;; \f
+;;; Parts
(defun notmuch-show-insert-bodypart-internal (msg part content-type nth depth button)
;; Run the handlers until one of them succeeds.
(defun notmuch-show-mime-type (part)
"Return the correct mime-type to use for PART."
- (let ((content-type (downcase (plist-get part :content-type))))
+ (when-let ((content-type (plist-get part :content-type)))
+ (setq content-type (downcase content-type))
(or (and (string= content-type "application/octet-stream")
(notmuch-show-get-mime-type-of-application/octet-stream part))
(and (string= content-type "inline patch")
should return non-NIL if a header button should be inserted for
this part.")
-(defun notmuch-show-insert-header-p (part hide)
+(defun notmuch-show-insert-header-p (part _hide)
;; Show all part buttons except for the first part if it is text/plain.
(let ((mime-type (notmuch-show-mime-type part)))
(not (and (string= mime-type "text/plain")
(<= (plist-get part :id) 1)))))
-(defun notmuch-show-reply-insert-header-p-never (part hide)
+(defun notmuch-show-reply-insert-header-p-never (_part _hide)
nil)
(defun notmuch-show-reply-insert-header-p-trimmed (part hide)
HIDE determines whether to show or hide the part and the button
as follows: If HIDE is nil, show the part and the button. If HIDE
is t, hide the part initially and show the button."
- (let* ((content-type (downcase (plist-get part :content-type)))
+ (let* ((content-type (plist-get part :content-type))
(mime-type (notmuch-show-mime-type part))
(nth (plist-get part :id))
(long (and (notmuch-match-content-type mime-type "text/*")
;; the first (or only) part if this is text/plain.
(button (and (funcall notmuch-show-insert-header-p-function part hide)
(notmuch-show-insert-part-header
- nth mime-type content-type
+ nth mime-type
+ (and content-type (downcase content-type))
(plist-get part :filename))))
;; Hide the part initially if HIDE is t, or if it is too long
;; and we have a button to allow toggling.
(notmuch-show-message-visible msg (and (plist-get msg :match)
(not (plist-get msg :excluded))))))
+;;; Toggle commands
+
(defun notmuch-show-toggle-process-crypto ()
"Toggle the processing of cryptographic MIME parts."
(interactive)
"Content is not indented."))
(notmuch-show-refresh-view))
+;;; Main insert functions
+
(defun notmuch-show-insert-tree (tree depth)
"Insert the message tree TREE at depth DEPTH in the current thread."
(let ((msg (car tree))
"Insert the forest of threads FOREST."
(mapc (lambda (thread) (notmuch-show-insert-thread thread 0)) forest))
+;;; Link buttons
+
(defvar notmuch-id-regexp
(concat
;; Match the id: prefix only if it begins a word (to disallow, for
'help-echo "Mouse-1, RET: search for this message"
'face goto-address-mail-face)))))
+;;; Show command
+
;;;###autoload
(defun notmuch-show (thread-id &optional elide-toggle parent-buffer query-context buffer-name)
"Run \"notmuch show\" with the given thread ID and display results.
;; Report back to the caller whether any messages matched.
forest))
+;;; Refresh command
+
(defun notmuch-show-capture-state ()
"Capture the state of the current buffer.
(ding)
(message "Refreshing the buffer resulted in no messages!"))))
+;;; Keymaps
+
(defvar notmuch-show-stash-map
(let ((map (make-sparse-keymap)))
(define-key map "c" 'notmuch-show-stash-cc)
(define-key map "B" 'notmuch-show-browse-urls)
map)
"Keymap for \"notmuch show\" buffers.")
-(fset 'notmuch-show-mode-map notmuch-show-mode-map)
+
+;;; Mode
(define-derived-mode notmuch-show-mode fundamental-mode "notmuch-show"
"Major mode for viewing a thread with notmuch.
(setq imenu-extract-index-name-function
#'notmuch-show-imenu-extract-index-name-function))
+;;; Tree commands
+
(defun notmuch-tree-from-show-current-query ()
"Call notmuch tree with the current query."
(interactive)
notmuch-show-query-context
(notmuch-show-get-message-id)))
+;;; Movement related functions.
+
(defun notmuch-show-move-to-message-top ()
(goto-char (notmuch-show-message-top)))
(defun notmuch-show-move-to-message-bottom ()
(goto-char (notmuch-show-message-bottom)))
-(defun notmuch-show-message-adjust ()
- (recenter 0))
-
-;; Movement related functions.
-
;; There's some strangeness here where a text property applied to a
;; region a->b is not found when point is at b. We walk backwards
;; until finding the property.
(cl-loop do (funcall function)
while (notmuch-show-goto-message-next))))
-;; Functions relating to the visibility of messages and their
-;; components.
+;;; Functions relating to the visibility of messages and their components.
(defun notmuch-show-message-visible (props visible-p)
(overlay-put (plist-get props :message-overlay) 'invisible (not visible-p))
(overlay-put (plist-get props :headers-overlay) 'invisible (not visible-p))
(notmuch-show-set-prop :headers-visible visible-p props))
-;; Functions for setting and getting attributes of the current
-;; message.
+;;; Functions for setting and getting attributes of the current message.
(defun notmuch-show-set-message-properties (props)
(save-excursion
message in either tree or show. This means that several utility
functions in notmuch-show can be used directly by notmuch-tree as
they just need the correct message properties."
- (let ((props (or props
- (cond ((eq major-mode 'notmuch-show-mode)
- (notmuch-show-get-message-properties))
- ((eq major-mode 'notmuch-tree-mode)
- (notmuch-tree-get-message-properties))
- (t nil)))))
- (plist-get props prop)))
+ (plist-get (or props
+ (cond ((eq major-mode 'notmuch-show-mode)
+ (notmuch-show-get-message-properties))
+ ((eq major-mode 'notmuch-tree-mode)
+ (notmuch-tree-get-message-properties))
+ (t nil)))
+ prop))
(defun notmuch-show-get-message-id (&optional bare)
"Return an id: query for the Message-Id of the current message.
(apply 'notmuch-show-tag-message
(notmuch-tag-change-list notmuch-show-mark-read-tags unread))))
-(defun notmuch-show-seen-current-message (start end)
+(defun notmuch-show-seen-current-message (_start _end)
"Mark the current message read if it is open.
We only mark it read once: if it is changed back then that is a
;; We need to redisplay to get window-start and window-end correct.
(redisplay)
(save-excursion
- (condition-case err
+ (condition-case nil
(funcall notmuch-show-mark-read-function (window-start) (window-end))
((debug error)
(unless notmuch-show--seen-has-errored
- (setq notmuch-show--seen-has-errored 't)
+ (setq notmuch-show--seen-has-errored t)
(setq header-line-format
(concat header-line-format
(propertize
Reshows the current thread with matches defined by the new query-string."
(interactive (list (notmuch-read-query "Filter thread: ")))
(let ((msg-id (notmuch-show-get-message-id)))
- (setq notmuch-show-query-context (if (string= query "") nil query))
+ (setq notmuch-show-query-context (if (string-empty-p query) nil query))
(notmuch-show-refresh-view t)
(notmuch-show-goto-message msg-id)))
-;; Functions for getting attributes of several messages in the current
-;; thread.
+;;; Functions for getting attributes of several messages in the current thread.
(defun notmuch-show-get-message-ids-for-open-messages ()
"Return a list of all id: queries for open messages in the current thread."
(setq done (not (notmuch-show-goto-message-next))))
message-ids)))
-;; Commands typically bound to keys.
+;;; Commands typically bound to keys.
(defun notmuch-show-advance ()
"Advance through thread.
(message-resend addresses)
(notmuch-bury-or-kill-this-buffer)))
+(defun notmuch-show-message-adjust ()
+ (recenter 0))
+
(defun notmuch-show-next-message (&optional pop-at-end)
"Show the next message.
(browse-url (current-kill 0 t)))
(defun notmuch-show-stash-git-helper (addresses prefix)
- "Escape, trim, quote, and add PREFIX to each address in list of ADDRESSES, and return the result as a single string."
+ "Normalize all ADDRESSES while adding PREFIX.
+Escape, trim, quote and add PREFIX to each address in list
+of ADDRESSES, and return the result as a single string."
(mapconcat (lambda (x)
(concat prefix "\""
;; escape double-quotes
addresses " "))
(put 'notmuch-show-stash-git-send-email 'notmuch-prefix-doc
- "Copy From/To/Cc of current message to kill-ring in a form suitable for pasting to git send-email command line.")
+ "Copy From/To/Cc of current message to kill-ring.
+Use a form suitable for pasting to git send-email command line.")
(defun notmuch-show-stash-git-send-email (&optional no-in-reply-to)
- "Copy From/To/Cc/Message-Id of current message to kill-ring in a form suitable for pasting to git send-email command line.
+ "Copy From/To/Cc/Message-Id of current message to kill-ring.
+Use a form suitable for pasting to git send-email command line.
If invoked with a prefix argument (or NO-IN-REPLY-TO is non-nil),
omit --in-reply-to=<Message-Id>."
(list (notmuch-show-get-message-id t)) "--in-reply-to="))))
" ")))
-;; Interactive part functions and their helpers
+;;; Interactive part functions and their helpers
(defun notmuch-show-generate-part-buffer (msg part)
"Return a temporary buffer containing the specified part's content."
is destroyed when FN returns. If MIME-TYPE is given then force
part to be treated as if it had that mime-type."
(let ((handle (notmuch-show-current-part-handle mime-type)))
- ;; emacs 24.3+ puts stdout/stderr into the calling buffer so we
- ;; call it from a temp-buffer, unless
- ;; notmuch-show-attachment-debug is non-nil in which case we put
- ;; it in " *notmuch-part*".
+ ;; Emacs puts stdout/stderr into the calling buffer so we call
+ ;; it from a temp-buffer, unless notmuch-show-attachment-debug
+ ;; is non-nil, in which case we put it in " *notmuch-part*".
(unwind-protect
(if notmuch-show-attachment-debug
(with-current-buffer (generate-new-buffer " *notmuch-part*")
(funcall fn (completing-read prompt urls nil nil nil nil (car urls)))
(message "No URLs found."))))
+;;; _
+
(provide 'notmuch-show)
;;; notmuch-show.el ends here
-;;; notmuch-tag.el --- tag messages within emacs
+;;; notmuch-tag.el --- tag messages within emacs -*- lexical-binding: t -*-
;;
;; Copyright © Damien Cassou
;; Copyright © Carl Worth
;;; Code:
-(require 'cl-lib)
-(eval-when-compile
- (require 'pcase))
-
(require 'crm)
(require 'notmuch-lib)
(declare-function notmuch-tree-tag "notmuch-tree" (tag-changes))
(declare-function notmuch-jump "notmuch-jump" (action-map prompt))
+;;; Keys
+
(define-widget 'notmuch-tag-key-type 'list
"A single key tagging binding."
:format "%v"
be used (either as a key, or as the start of a key sequence) as
it is already bound: it switches the menu to a menu of the
reverse tagging operations. The reverse of a tagging operation is
-the same list of individual tag-ops but with `+tag` replaced by
-`-tag` and vice versa.
+the same list of individual tag-ops but with `+tag' replaced by
+`-tag' and vice versa.
If setting this variable outside of customize then it should be a
list of triples (lists of three elements). Each triple should be
of the form (key-binding tagging-operations name). KEY-BINDING
can be a single character or a key sequence; TAGGING-OPERATIONS
should either be a list of individual tag operations each of the
-form `+tag` or `-tag`, or the variable name of a variable that is
+form `+tag' or `-tag', or the variable name of a variable that is
a list of tagging operations; NAME should be a name for the
tagging operation, if omitted or empty than then name is taken
from TAGGING-OPERATIONS."
:type '(repeat notmuch-tag-key-type)
:group 'notmuch-tag)
+;;; Faces and Formats
+
(define-widget 'notmuch-tag-format-type 'lazy
"Customize widget for notmuch-tag-format and friends."
:type '(alist :key-type (regexp :tag "Tag")
'((t :foreground "red"))
"Default face used for the unread tag.
-Used in the default value of `notmuch-tag-formats`."
+Used in the default value of `notmuch-tag-formats'."
:group 'notmuch-faces)
(defface notmuch-tag-flagged
(:foreground "blue")))
"Face used for the flagged tag.
-Used in the default value of `notmuch-tag-formats`."
+Used in the default value of `notmuch-tag-formats'."
:group 'notmuch-faces)
(defcustom notmuch-tag-formats
(notmuch-tag-format-image-data tag (notmuch-tag-star-icon))))
"Custom formats for individual tags.
-This is an association list that maps from tag name regexps to
-lists of formatting expressions. The first entry whose car
-regexp-matches a tag will be used to format that tag. The regexp
-is implicitly anchored, so to match a literal tag name, just use
-that tag name (if it contains special regexp characters like
-\".\" or \"*\", these have to be escaped). The cdr of the
-matching entry gives a list of Elisp expressions that modify the
-tag. If the list is empty, the tag will simply be hidden.
-Otherwise, each expression will be evaluated in order: for the
-first expression, the variable `tag' will be bound to the tag
-name; for each later expression, the variable `tag' will be bound
-to the result of the previous expression. In this way, each
+This is an association list of the form ((MATCH EXPR...)...),
+mapping tag name regexps to lists of formatting expressions.
+
+The first entry whose MATCH regexp-matches a tag is used to
+format that tag. The regexp is implicitly anchored, so to match
+a literal tag name, just use that tag name (if it contains
+special regexp characters like \".\" or \"*\", these have to be
+escaped).
+
+The cdr of the matching entry gives a list of Elisp expressions
+that modify the tag. If the list is empty, the tag is simply
+hidden. Otherwise, each expression EXPR is evaluated in order:
+for the first expression, the variable `tag' is bound to the tag
+name; for each later expression, the variable `tag' is bound to
+the result of the previous expression. In this way, each
expression can build on the formatting performed by the previous
-expression. The result of the last expression will displayed in
+expression. The result of the last expression is displayed in
place of the tag.
For example, to replace a tag with another string, simply use
(t :inverse-video t))
"Face used to display deleted tags.
-Used in the default value of `notmuch-tag-deleted-formats`."
+Used in the default value of `notmuch-tag-deleted-formats'."
:group 'notmuch-faces)
(defcustom notmuch-tag-deleted-formats
(".*" (notmuch-apply-face tag `notmuch-tag-deleted)))
"Custom formats for tags when deleted.
-For deleted tags the formats in `notmuch-tag-formats` are applied
+For deleted tags the formats in `notmuch-tag-formats' are applied
first and then these formats are applied on top; that is `tag'
passed to the function is the tag with all these previous
formattings applied. The formatted can access the original
'((t :underline "green"))
"Default face used for added tags.
-Used in the default value for `notmuch-tag-added-formats`."
+Used in the default value for `notmuch-tag-added-formats'."
:group 'notmuch-faces)
(defcustom notmuch-tag-added-formats
'((".*" (notmuch-apply-face tag 'notmuch-tag-added)))
"Custom formats for tags when added.
-For added tags the formats in `notmuch-tag-formats` are applied
+For added tags the formats in `notmuch-tag-formats' are applied
first and then these formats are applied on top.
To disable special formatting of added tags, set this variable to
:group 'notmuch-faces
:type 'notmuch-tag-format-type)
+;;; Icons
+
(defun notmuch-tag-format-image-data (tag data)
"Replace TAG with image DATA, if available.
</g>
</svg>")
+;;; Format Handling
+
(defvar notmuch-tag--format-cache (make-hash-table :test 'equal)
"Cache of tag format lookup. Internal to `notmuch-tag-format-tag'.")
"Clear the internal cache of tag formats."
(clrhash notmuch-tag--format-cache))
-(defun notmuch-tag--get-formats (tag format-alist)
+(defun notmuch-tag--get-formats (tag alist)
"Find the first item whose car regexp-matches TAG."
(save-match-data
;; Don't use assoc-default since there's no way to distinguish a
;; missing key from a present key with a null cdr.
- (cl-assoc tag format-alist
+ (cl-assoc tag alist
:test (lambda (tag key)
(and (eq (string-match key tag) 0)
(= (match-end 0) (length tag)))))))
-(defun notmuch-tag--do-format (tag formatted-tag formats)
+(defun notmuch-tag--do-format (bare-tag tag formats)
"Apply a tag-formats entry to TAG."
(cond ((null formats) ;; - Tag not in `formats',
- formatted-tag) ;; the format is the tag itself.
+ tag) ;; the format is the tag itself.
((null (cdr formats)) ;; - Tag was deliberately hidden,
nil) ;; no format must be returned
(t
;; Tag was found and has formats, we must apply all the
;; formats. TAG may be null so treat that as a special case.
- (let ((bare-tag tag)
- (tag (copy-sequence (or formatted-tag ""))))
+ (let ((return-tag (copy-sequence (or tag ""))))
(dolist (format (cdr formats))
- (setq tag (eval format)))
- (if (and (null formatted-tag) (equal tag ""))
+ (setq return-tag
+ (eval format
+ `((bare-tag . ,bare-tag)
+ (tag . ,return-tag)))))
+ (if (and (null tag) (equal return-tag ""))
nil
- tag)))))
+ return-tag)))))
(defun notmuch-tag-format-tag (tags orig-tags tag)
"Format TAG according to `notmuch-tag-formats'.
face
t)))
+;;; Hooks
+
(defcustom notmuch-before-tag-hook nil
"Hooks that are run before tags of a message are modified.
:options '(notmuch-hl-line-mode)
:group 'notmuch-hooks)
+;;; User Input
+
(defvar notmuch-select-tag-history nil
- "Variable to store minibuffer history for
-`notmuch-select-tag-with-completion' function.")
+ "Minibuffer history of `notmuch-select-tag-with-completion' function.")
(defvar notmuch-read-tag-changes-history nil
- "Variable to store minibuffer history for
-`notmuch-read-tag-changes' function.")
+ "Minibuffer history of `notmuch-read-tag-changes' function.")
(defun notmuch-tag-completions (&rest search-terms)
"Return a list of tags for messages matching SEARCH-TERMS.
-Returns all tags if no search terms are given."
+Return all tags if no search terms are given."
(unless search-terms
(setq search-terms (list "*")))
(split-string
"\n+" t))
(defun notmuch-select-tag-with-completion (prompt &rest search-terms)
- (let ((tag-list (apply #'notmuch-tag-completions search-terms)))
- (completing-read prompt tag-list nil nil nil 'notmuch-select-tag-history)))
+ (completing-read prompt
+ (apply #'notmuch-tag-completions search-terms)
+ nil nil nil 'notmuch-select-tag-history))
(defun notmuch-read-tag-changes (current-tags &optional prompt initial-input)
"Prompt for tag changes in the minibuffer.
-CURRENT-TAGS is a list of tags that are present on the message or
-messages to be changed. These are offered as tag removal
+CURRENT-TAGS is a list of tags that are present on the message
+or messages to be changed. These are offered as tag removal
completions. CURRENT-TAGS may contain duplicates. PROMPT, if
non-nil, is the query string to present in the minibuffer. It
defaults to \"Tags\". INITIAL-INPUT, if non-nil, will be the
nil nil initial-input
'notmuch-read-tag-changes-history))))
+;;; Tagging
+
(defun notmuch-update-tags (tags tag-changes)
"Return a copy of TAGS with additions and removals from TAG-CHANGES.
from TAGS if present."
(let ((result-tags (copy-sequence tags)))
(dolist (tag-change tag-changes)
- (let ((op (string-to-char tag-change))
- (tag (unless (string= tag-change "") (substring tag-change 1))))
- (cl-case op
+ (let ((tag (and (not (string-empty-p tag-change))
+ (substring tag-change 1))))
+ (cl-case (aref tag-change 0)
(?+ (unless (member tag result-tags)
(push tag result-tags)))
(?- (setq result-tags (delete tag result-tags)))
directly, so that hooks specified in notmuch-before-tag-hook and
notmuch-after-tag-hook will be run."
;; Perform some validation
- (mapc (lambda (tag-change)
- (unless (string-match-p "^[-+]\\S-+$" tag-change)
- (error "Tag must be of the form `+this_tag' or `-that_tag'")))
- tag-changes)
+ (dolist (tag-change tag-changes)
+ (unless (string-match-p "^[-+]\\S-+$" tag-change)
+ (error "Tag must be of the form `+this_tag' or `-that_tag'")))
(unless query
(error "Nothing to tag!"))
- (unless (null tag-changes)
- (run-hooks 'notmuch-before-tag-hook)
+ (when tag-changes
+ (notmuch-dlet ((tag-changes tag-changes)
+ (query query))
+ (run-hooks 'notmuch-before-tag-hook))
(if (<= (length query) notmuch-tag-argument-limit)
(apply 'notmuch-call-notmuch-process "tag"
(append tag-changes (list "--" query)))
(let ((batch-op (concat (mapconcat #'notmuch-hex-encode tag-changes " ")
" -- " query)))
(notmuch-call-notmuch-process :stdin-string batch-op "tag" "--batch")))
- (run-hooks 'notmuch-after-tag-hook)))
+ (notmuch-dlet ((tag-changes tag-changes)
+ (query query))
+ (run-hooks 'notmuch-after-tag-hook))))
(defun notmuch-tag-change-list (tags &optional reverse)
"Convert TAGS into a list of tag changes.
Creates and displays a jump menu for the tagging operations
specified in `notmuch-tagging-keys'. If REVERSE is set then it
offers a menu of the reverses of the operations specified in
-`notmuch-tagging-keys'; i.e. each `+tag` is replaced by `-tag`
+`notmuch-tagging-keys'; i.e. each `+tag' is replaced by `-tag'
and vice versa."
;; In principle this function is simple, but it has to deal with
;; lots of cases: different modes (search/show/tree), whether a name
(symbol-value tag)
tag))
(tag-change (if reverse
- (notmuch-tag-change-list tag 't)
+ (notmuch-tag-change-list tag t)
tag))
(name (or (and (not (string= name ""))
name)
(setq action-map (nreverse action-map))
(notmuch-jump action-map "Tag: ")))
-;;
+;;; _
(provide 'notmuch-tag)
-;;; notmuch-tree.el --- displaying notmuch forests
+;;; notmuch-tree.el --- displaying notmuch forests -*- lexical-binding: t -*-
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
;;; Code:
-(eval-when-compile (require 'cl-lib))
-
(require 'mail-parse)
(require 'notmuch-lib)
(declare-function notmuch-search-find-thread-id "notmuch" (&optional bare))
(declare-function notmuch-search-find-subject "notmuch" ())
+;; For `notmuch-tree-next-thread-from-search'.
+(declare-function notmuch-search-next-thread "notmuch" ())
+(declare-function notmuch-search-previous-thread "notmuch" ())
+(declare-function notmuch-tree-from-search-thread "notmuch" ())
+
;; the following variable is defined in notmuch.el
(defvar notmuch-search-query-string)
;; this variable distinguishes the unthreaded display from the normal tree display
-(defvar notmuch-tree-unthreaded nil
+(defvar-local notmuch-tree-unthreaded nil
"A buffer local copy of argument unthreaded to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-unthreaded)
+
+;;; Options
(defgroup notmuch-tree nil
"Showing message and thread structure."
notmuch-unthreaded-result-format
notmuch-tree-result-format))
-;; Faces for messages that match the query.
+;;; Faces
+;;;; Faces for messages that match the query
+
(defface notmuch-tree-match-face
'((t :inherit default))
"Default face used in tree mode face for matching messages"
:group 'notmuch-tree
:group 'notmuch-faces)
-;; Faces for messages that do not match the query.
+;;;; Faces for messages that do not match the query
+
(defface notmuch-tree-no-match-face
'((t (:foreground "gray")))
"Default face used in tree mode face for non-matching messages."
:group 'notmuch-tree
:group 'notmuch-faces)
-(defvar notmuch-tree-previous-subject
+;;; Variables
+
+(defvar-local notmuch-tree-previous-subject
"The subject of the most recent result shown during the async display.")
-(make-variable-buffer-local 'notmuch-tree-previous-subject)
-(defvar notmuch-tree-basic-query nil
+(defvar-local notmuch-tree-basic-query nil
"A buffer local copy of argument query to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-basic-query)
-(defvar notmuch-tree-query-context nil
+(defvar-local notmuch-tree-query-context nil
"A buffer local copy of argument query-context to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-query-context)
-(defvar notmuch-tree-target-msg nil
+(defvar-local notmuch-tree-target-msg nil
"A buffer local copy of argument target to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-target-msg)
-(defvar notmuch-tree-open-target nil
+(defvar-local notmuch-tree-open-target nil
"A buffer local copy of argument open-target to the function notmuch-tree.")
-(make-variable-buffer-local 'notmuch-tree-open-target)
-(defvar notmuch-tree-parent-buffer nil)
-(make-variable-buffer-local 'notmuch-tree-parent-buffer)
+(defvar-local notmuch-tree-parent-buffer nil)
-(defvar notmuch-tree-message-window nil
+(defvar-local notmuch-tree-message-window nil
"The window of the message pane.
It is set in both the tree buffer and the child show buffer. It
is used to try and close the message pane when quitting tree view
or the child show buffer.")
-(make-variable-buffer-local 'notmuch-tree-message-window)
(put 'notmuch-tree-message-window 'permanent-local t)
-(defvar notmuch-tree-message-buffer nil
+(defvar-local notmuch-tree-message-buffer nil
"The buffer name of the show buffer in the message pane.
This is used to try and make sure we don't close the message pane
if the user has loaded a different buffer in that window.")
-(make-variable-buffer-local 'notmuch-tree-message-buffer)
(put 'notmuch-tree-message-buffer 'permanent-local t)
-(defun notmuch-tree-to-message-pane (func)
- "Execute FUNC in message pane.
+;;; Tree wrapper commands
-This function returns a function (so can be used as a keybinding)
-which executes function FUNC in the message pane if it is
-open (if the message pane is closed it does nothing)."
- `(lambda ()
- ,(concat "(In message pane) " (documentation func t))
+(defmacro notmuch-tree--define-do-in-message-window (name cmd)
+ "Define NAME as a command that calls CMD interactively in the message window.
+If the message pane is closed then this command does nothing.
+Avoid using this macro in new code; it will be removed."
+ `(defun ,name ()
+ ,(concat "(In message window) " (documentation cmd t))
(interactive)
(when (window-live-p notmuch-tree-message-window)
(with-selected-window notmuch-tree-message-window
- (call-interactively #',func)))))
-
-(defun notmuch-tree-inherit-from-message-pane (sym)
- "Return value of SYM in message-pane if open, or tree-pane if not."
+ (call-interactively #',cmd)))))
+
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-previous-message-button
+ notmuch-show-previous-button)
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-next-message-button
+ notmuch-show-next-button)
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-toggle-message-process-crypto
+ notmuch-show-toggle-process-crypto)
+
+(defun notmuch-tree--message-process-crypto ()
+ "Return value of `notmuch-show-process-crypto' in the message window.
+If that window isn't alive, then return the current value.
+Avoid using this function in new code; it will be removed."
(if (window-live-p notmuch-tree-message-window)
(with-selected-window notmuch-tree-message-window
- (symbol-value sym))
- (symbol-value sym)))
-
-(defun notmuch-tree-button-activate (&optional button)
- "Activate BUTTON or button at point.
-
-This function does not give an error if there is no button."
- (interactive)
- (let ((button (or button (button-at (point)))))
- (when button (button-activate button))))
-
-(defun notmuch-tree-close-message-pane-and (func)
- "Close message pane and execute FUNC.
-
-This function returns a function (so can be used as a keybinding)
-which closes the message pane if open and then executes function
-FUNC."
- `(lambda ()
- ,(concat "(Close message pane and) " (documentation func t))
+ notmuch-show-process-crypto)
+ notmuch-show-process-crypto))
+
+(defmacro notmuch-tree--define-close-message-window-and (name cmd)
+ "Define NAME as a variant of CMD.
+
+NAME determines the value of `notmuch-show-process-crypto' in the
+message window, closes the window, and then call CMD interactively
+with that value let-bound. If the message window does not exist,
+then NAME behaves like CMD."
+ `(defun ,name ()
+ ,(concat "(Close message pane and) " (documentation cmd t))
(interactive)
(let ((notmuch-show-process-crypto
- (notmuch-tree-inherit-from-message-pane 'notmuch-show-process-crypto)))
+ (notmuch-tree--message-process-crypto)))
(notmuch-tree-close-message-window)
- (call-interactively #',func))))
+ (call-interactively #',cmd))))
+
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-help
+ notmuch-help)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-new-mail
+ notmuch-mua-new-mail)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-jump-search
+ notmuch-jump-search)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-forward-message
+ notmuch-show-forward-message)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-reply-sender
+ notmuch-show-reply-sender)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-reply
+ notmuch-show-reply)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-view-raw-message
+ notmuch-show-view-raw-message)
+
+;;; Keymap
(defvar notmuch-tree-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map notmuch-common-keymap)
- ;; The following override the global keymap.
- ;; Override because we want to close message pane first.
- (define-key map [remap notmuch-help]
- (notmuch-tree-close-message-pane-and #'notmuch-help))
- ;; Override because we first close message pane and then close tree buffer.
+ ;; These bindings shadow common bindings with variants
+ ;; that additionally close the message window.
(define-key map [remap notmuch-bury-or-kill-this-buffer] 'notmuch-tree-quit)
- ;; Override because we close message pane after the search query is entered.
- (define-key map [remap notmuch-search] 'notmuch-tree-to-search)
- ;; Override because we want to close message pane first.
- (define-key map [remap notmuch-mua-new-mail]
- (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
- ;; Override because we want to close message pane first.
- (define-key map [remap notmuch-jump-search]
- (notmuch-tree-close-message-pane-and #'notmuch-jump-search))
+ (define-key map [remap notmuch-search] 'notmuch-tree-to-search)
+ (define-key map [remap notmuch-help] 'notmuch-tree-help)
+ (define-key map [remap notmuch-mua-new-mail] 'notmuch-tree-new-mail)
+ (define-key map [remap notmuch-jump-search] 'notmuch-tree-jump-search)
(define-key map "S" 'notmuch-search-from-tree-current-query)
(define-key map "U" 'notmuch-unthreaded-from-tree-current-query)
(define-key map "b" 'notmuch-show-resend-message)
;; these apply to the message pane
- (define-key map (kbd "M-TAB")
- (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
- (define-key map (kbd "<backtab>")
- (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
- (define-key map (kbd "TAB")
- (notmuch-tree-to-message-pane #'notmuch-show-next-button))
- (define-key map "$"
- (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto))
+ (define-key map (kbd "M-TAB") 'notmuch-tree-previous-message-button)
+ (define-key map (kbd "<backtab>") 'notmuch-tree-previous-message-button)
+ (define-key map (kbd "TAB") 'notmuch-tree-next-message-button)
+ (define-key map "$" 'notmuch-tree-toggle-message-process-crypto)
;; bindings from show (or elsewhere) but we close the message pane first.
- (define-key map "f"
- (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message))
- (define-key map "r"
- (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))
- (define-key map "R"
- (notmuch-tree-close-message-pane-and #'notmuch-show-reply))
- (define-key map "V"
- (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))
+ (define-key map "f" 'notmuch-tree-forward-message)
+ (define-key map "r" 'notmuch-tree-reply-sender)
+ (define-key map "R" 'notmuch-tree-reply)
+ (define-key map "V" 'notmuch-tree-view-raw-message)
;; The main tree view bindings
(define-key map (kbd "RET") 'notmuch-tree-show-message)
(define-key map " " 'notmuch-tree-scroll-or-next)
(define-key map (kbd "DEL") 'notmuch-tree-scroll-message-window-back)
(define-key map "e" 'notmuch-tree-resume-message)
- map))
-(fset 'notmuch-tree-mode-map notmuch-tree-mode-map)
+ map)
+ "Keymap for \"notmuch tree\" buffers.")
+
+;;; Message properties
(defun notmuch-tree-get-message-properties ()
"Return the properties of the current message as a plist.
(notmuch-tree-set-message-properties props)))
(defun notmuch-tree-get-prop (prop &optional props)
- (let ((props (or props
- (notmuch-tree-get-message-properties))))
- (plist-get props prop)))
+ (plist-get (or props (notmuch-tree-get-message-properties))
+ prop))
(defun notmuch-tree-set-tags (tags)
"Set the tags of the current message."
(defun notmuch-tree-get-match ()
"Return whether the current message is a match."
- (interactive)
(notmuch-tree-get-prop :match))
+;;; Update display
+
(defun notmuch-tree-refresh-result ()
"Redisplay the current message line.
(when (string= tree-msg-id (notmuch-show-get-message-id))
(notmuch-show-update-tags new-tags)))))))
+;;; Commands (and some helper functions used by them)
+
(defun notmuch-tree-tag (tag-changes)
"Change tags for the current message."
(interactive
(let ((buffer (current-buffer)))
(when (and (window-live-p notmuch-tree-message-window)
(eq (window-buffer notmuch-tree-message-window) buffer))
- ;; We do not want an error if this is the sole window in the
- ;; frame and I do not know how to test for that in emacs pre
- ;; 24. Hence we just ignore-errors.
+ ;; We could check whether this is the only window in its frame,
+ ;; but simply ignoring the error that is thrown otherwise is
+ ;; what we had to do for Emacs 24 and we stick to that because
+ ;; it is still the simplest approach.
(ignore-errors
(delete-window notmuch-tree-message-window)))))
"Show the current message (in whole window)."
(interactive)
(let ((id (notmuch-tree-get-message-id))
- (inhibit-read-only t)
- buffer)
+ (inhibit-read-only t))
(when id
;; We close the window to kill off un-needed buffers.
(notmuch-tree-close-message-window)
(notmuch-tree-from-search-thread))))
(defun notmuch-tree-next-thread (&optional previous)
- "Move to the next thread in the current tree or parent search
-results
+ "Move to the next thread in the current tree or parent search results.
If PREVIOUS is non-nil, move to the previous thread in the tree or
search results instead."
(notmuch-tree-next-thread-from-search previous)))
(defun notmuch-tree-prev-thread ()
- "Move to the previous thread in the current tree or parent search
-results"
+ "Move to the previous thread in the current tree or parent search results."
(interactive)
(notmuch-tree-next-thread t))
(defun notmuch-tree-thread-mapcar (function)
- "Iterate through all messages in the current thread
- and call FUNCTION for side effects."
+ "Call FUNCTION for each message in the current thread.
+FUNCTION is called for side effects only."
(save-excursion
(notmuch-tree-thread-top)
(cl-loop collect (funcall function)
(notmuch-tree-tag-thread
(notmuch-tag-change-list notmuch-archive-tags unarchive))))
-;; Functions below here display the tree buffer itself.
+;;; Functions for displaying the tree buffer itself
(defun notmuch-tree-clean-address (address)
"Try to clean a single email ADDRESS for display. Return
(setq buffer-read-only t)
(setq truncate-lines t))
-(defun notmuch-tree-process-sentinel (proc msg)
+(defun notmuch-tree-process-sentinel (proc _msg)
"Add a message to let user know when \"notmuch tree\" exits."
(let ((buffer (process-buffer proc))
(status (process-status proc))
- (exit-status (process-exit-status proc))
- (never-found-target-thread nil))
+ (exit-status (process-exit-status proc)))
(when (memq status '(exit signal))
(kill-buffer (process-get proc 'parse-buf))
(when (buffer-live-p buffer)
(with-current-buffer buffer
(save-excursion
- (let ((inhibit-read-only t)
- (atbob (bobp)))
+ (let ((inhibit-read-only t))
(goto-char (point-max))
(when (eq status 'signal)
(insert "Incomplete search results (tree view process was killed).\n"))
"Process and filter the output of \"notmuch show\" for tree view."
(let ((results-buf (process-buffer proc))
(parse-buf (process-get proc 'parse-buf))
- (inhibit-read-only t)
- done)
+ (inhibit-read-only t))
(if (not (buffer-live-p results-buf))
(delete-process proc)
(with-current-buffer parse-buf
(inhibit-read-only t))
(pop-to-buffer-same-window buffer))
;; Don't track undo information for this buffer
- (set 'buffer-undo-list t)
+ (setq buffer-undo-list t)
(notmuch-tree-worker query query-context target open-target unthreaded)
(setq notmuch-tree-parent-buffer parent-buffer)
(setq truncate-lines t))
(interactive)
(notmuch-tree query query-context target buffer-name open-target t))
-;;
+;;; _
(provide 'notmuch-tree)
-;;; notmuch-wash.el --- cleaning up message bodies
+;;; notmuch-wash.el --- cleaning up message bodies -*- lexical-binding: t -*-
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
;;; Code:
(require 'coolj)
+(require 'diff-mode)
(require 'notmuch-lib)
(declare-function notmuch-show-insert-bodypart "notmuch-show"
(msg part depth &optional hide))
(defvar notmuch-show-indent-messages-width)
-;;
+;;; Options
(defgroup notmuch-wash nil
"Cleaning up messages for display."
(integer :tag "number of characters"))
:group 'notmuch-wash)
+;;; Faces
+
(defface notmuch-wash-toggle-button
'((t (:inherit font-lock-comment-face)))
"Face used for buttons toggling the visibility of washed away
:group 'notmuch-wash
:group 'notmuch-faces)
+;;; Buttons
+
(defun notmuch-wash-toggle-invisible-action (cite-button)
;; Toggle overlay visibility
(let ((overlay (button-get cite-button 'overlay)))
(overlay-end overlay))))
(format label-format lines-count)))
-(defun notmuch-wash-region-to-button (msg beg end type &optional prefix)
+(defun notmuch-wash-region-to-button (beg end type &optional prefix)
"Auxiliary function to do the actual making of overlays and buttons.
BEG and END are buffer locations. TYPE should a string, either
:type button-type)))
(overlay-put overlay 'notmuch-wash-button button))))))
-(defun notmuch-wash-excerpt-citations (msg depth)
+;;; Hook functions
+
+(defun notmuch-wash-excerpt-citations (_msg _depth)
"Excerpt citations and up to one signature."
(goto-char (point-min))
(beginning-of-line)
(when (and (< (point) (point-max))
(re-search-forward notmuch-wash-original-regexp nil t))
- (let* ((msg-start (match-beginning 0))
- (msg-end (point-max))
- (msg-lines (count-lines msg-start msg-end)))
- (notmuch-wash-region-to-button
- msg msg-start msg-end "original")))
+ (notmuch-wash-region-to-button (match-beginning 0)
+ (point-max)
+ "original"))
(while (and (< (point) (point-max))
(re-search-forward notmuch-wash-citation-regexp nil t))
(let* ((cite-start (match-beginning 0))
(goto-char cite-end)
(forward-line (- notmuch-wash-citation-lines-suffix))
(notmuch-wash-region-to-button
- msg hidden-start (point-marker)
+ hidden-start (point-marker)
"citation")))))
(when (and (not (eobp))
(re-search-forward notmuch-wash-signature-regexp nil t))
- (let* ((sig-start (match-beginning 0))
- (sig-end (match-end 0))
- (sig-lines (count-lines sig-start (point-max))))
- (when (<= sig-lines notmuch-wash-signature-lines-max)
+ (let ((sig-start (match-beginning 0)))
+ (when (<= (count-lines sig-start (point-max))
+ notmuch-wash-signature-lines-max)
(let ((sig-start-marker (make-marker))
(sig-end-marker (make-marker)))
(set-marker sig-start-marker sig-start)
(overlay-put (make-overlay sig-start-marker sig-end-marker)
'face 'message-cited-text)
(notmuch-wash-region-to-button
- msg sig-start-marker sig-end-marker
+ sig-start-marker sig-end-marker
"signature"))))))
-;;
-
-(defun notmuch-wash-elide-blank-lines (msg depth)
+(defun notmuch-wash-elide-blank-lines (_msg _depth)
"Elide leading, trailing and successive blank lines."
;; Algorithm derived from `article-strip-multiple-blank-lines' in
;; `gnus-art.el'.
(when (looking-at "\n")
(delete-region (match-beginning 0) (match-end 0))))
-;;
-
-(defun notmuch-wash-tidy-citations (msg depth)
+(defun notmuch-wash-tidy-citations (_msg _depth)
"Improve the display of cited regions of a message.
Perform several transformations on the message body:
(while (re-search-forward "\\(^>[> ]*\n\\)\\(^$\\|^[^>].*\\)" nil t)
(replace-match "\\2")))
-;;
-
-(defun notmuch-wash-wrap-long-lines (msg depth)
+(defun notmuch-wash-wrap-long-lines (_msg depth)
"Wrap long lines in the message.
If `notmuch-wash-wrap-lines-length' is a number, this will wrap
2)))
(coolj-wrap-region (point-min) (point-max))))
-;;
-
-(require 'diff-mode)
-
-(defvar diff-file-header-re) ; From `diff-mode.el'.
+;;;; Convert Inline Patches
(defun notmuch-wash-subject-to-filename (subject &optional maxlen)
"Convert a mail SUBJECT into a filename.
(delete-region (point-min) (point-max))
(notmuch-show-insert-bodypart nil part depth)))))
-;;
+;;; _
(provide 'notmuch-wash)
-;;; notmuch.el --- run notmuch within emacs
+;;; notmuch.el --- run notmuch within emacs -*- lexical-binding: t -*-
;;
;; Copyright © Carl Worth
;;
;;; Code:
-(eval-when-compile (require 'cl-lib))
-
(require 'mm-view)
(require 'message)
+(require 'hl-line)
+
(require 'notmuch-lib)
(require 'notmuch-tag)
(require 'notmuch-show)
(require 'notmuch-message)
(require 'notmuch-parser)
+;;; Options
+
(defcustom notmuch-search-result-format
`(("date" . "%12s ")
("count" . "%-7s ")
:type 'file
:group 'notmuch)
-(defvar notmuch-query-history nil
- "Variable to store minibuffer history for notmuch queries.")
+(defcustom notmuch-search-hook '(notmuch-hl-line-mode)
+ "List of functions to call when notmuch displays the search results."
+ :type 'hook
+ :options '(notmuch-hl-line-mode)
+ :group 'notmuch-search
+ :group 'notmuch-hooks)
+
+;;; Mime Utilities
(defun notmuch-foreach-mime-part (function mm-handle)
(cond ((stringp (car mm-handle))
(mm-save-part p))))
mm-handle))
-(require 'hl-line)
-
-(defun notmuch-hl-line-mode ()
- (prog1 (hl-line-mode)
- (when hl-line-overlay
- (overlay-put hl-line-overlay 'priority 1))))
-
-(defcustom notmuch-search-hook '(notmuch-hl-line-mode)
- "List of functions to call when notmuch displays the search results."
- :type 'hook
- :options '(notmuch-hl-line-mode)
- :group 'notmuch-search
- :group 'notmuch-hooks)
+;;; Keymap
(defvar notmuch-search-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map notmuch-common-keymap)
(define-key map "x" 'notmuch-bury-or-kill-this-buffer)
- (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
+ (define-key map (kbd "DEL") 'notmuch-search-scroll-down)
(define-key map "b" 'notmuch-search-scroll-down)
(define-key map " " 'notmuch-search-scroll-up)
(define-key map "<" 'notmuch-search-first-thread)
(define-key map "U" 'notmuch-unthreaded-from-search-current-query)
map)
"Keymap for \"notmuch search\" buffers.")
-(fset 'notmuch-search-mode-map notmuch-search-mode-map)
+
+;;; Internal Variables
+
+(defvar notmuch-query-history nil
+ "Variable to store minibuffer history for notmuch queries.")
+
+(defvar-local notmuch-search-query-string nil)
+(defvar-local notmuch-search-target-thread nil)
+(defvar-local notmuch-search-target-line nil)
+
+;;; Stashing
(defvar notmuch-search-stash-map
(let ((map (make-sparse-keymap)))
(defun notmuch-stash-query ()
"Copy current query to kill-ring."
(interactive)
- (notmuch-common-do-stash (notmuch-search-get-query)))
-
-(defvar notmuch-search-query-string)
-(defvar notmuch-search-target-thread)
-(defvar notmuch-search-target-line)
+ (notmuch-common-do-stash notmuch-search-query-string))
-(defvar notmuch-search-disjunctive-regexp "\\<[oO][rR]\\>")
+;;; Movement
(defun notmuch-search-scroll-up ()
"Move forward through search results by one window's worth."
(interactive)
(goto-char (point-min)))
+;;; Faces
+
(defface notmuch-message-summary-face
`((((class color) (background light))
,@(and (>= emacs-major-version 27) '(:extend t))
"Face used in search mode face for flagged threads.
This face is the default value for the \"flagged\" tag in
-`notmuch-search-line-faces`."
+`notmuch-search-line-faces'."
:group 'notmuch-search
:group 'notmuch-faces)
"Face used in search mode for unread threads.
This face is the default value for the \"unread\" tag in
-`notmuch-search-line-faces`."
+`notmuch-search-line-faces'."
:group 'notmuch-search
:group 'notmuch-faces)
+;;; Mode
+
(define-derived-mode notmuch-search-mode fundamental-mode "notmuch-search"
"Major mode displaying results of a notmuch search.
Complete list of currently available key bindings:
\\{notmuch-search-mode-map}"
- (make-local-variable 'notmuch-search-query-string)
- (make-local-variable 'notmuch-search-oldest-first)
- (make-local-variable 'notmuch-search-target-thread)
- (make-local-variable 'notmuch-search-target-line)
(setq notmuch-buffer-refresh-function #'notmuch-search-refresh-view)
- (set (make-local-variable 'scroll-preserve-screen-position) t)
+ (setq-local scroll-preserve-screen-position t)
(add-to-invisibility-spec (cons 'ellipsis t))
(setq truncate-lines t)
(setq buffer-read-only t)
(setq imenu-extract-index-name-function
#'notmuch-search-imenu-extract-index-name-function))
+;;; Search Results
+
(defun notmuch-search-get-result (&optional pos)
"Return the result object for the thread at POS (or point).
(defun notmuch-search-find-stable-query ()
"Return the stable queries for the current thread.
-This returns a list (MATCHED-QUERY UNMATCHED-QUERY) for the
+Return a list (MATCHED-QUERY UNMATCHED-QUERY) for the
matched and unmatched messages in the current thread."
(plist-get (notmuch-search-get-result) :query))
`notmuch-show-only-matching-messages' when displaying the
thread."
(interactive "P")
- (let ((thread-id (notmuch-search-find-thread-id))
- (subject (notmuch-search-find-subject)))
- (if (> (length thread-id) 0)
+ (let ((thread-id (notmuch-search-find-thread-id)))
+ (if thread-id
(notmuch-show thread-id
elide-toggle
(current-buffer)
notmuch-search-query-string
;; Name the buffer based on the subject.
- (concat "*"
- (truncate-string-to-width subject 30 nil nil t)
- "*"))
+ (format "*%s*" (truncate-string-to-width
+ (notmuch-search-find-subject)
+ 30 nil nil t)))
(message "End of search results."))))
(defun notmuch-tree-from-search-current-query ()
(defun notmuch-search-reply-to-thread (&optional prompt-for-sender)
"Begin composing a reply-all to the entire current thread in a new buffer."
(interactive "P")
- (let ((message-id (notmuch-search-find-thread-id)))
- (notmuch-mua-new-reply message-id prompt-for-sender t)))
+ (notmuch-mua-new-reply (notmuch-search-find-thread-id)
+ prompt-for-sender t))
(defun notmuch-search-reply-to-thread-sender (&optional prompt-for-sender)
"Begin composing a reply to the entire current thread in a new buffer."
(interactive "P")
- (let ((message-id (notmuch-search-find-thread-id)))
- (notmuch-mua-new-reply message-id prompt-for-sender nil)))
+ (notmuch-mua-new-reply (notmuch-search-find-thread-id)
+ prompt-for-sender nil))
+
+;;; Tags
(defun notmuch-search-set-tags (tags &optional pos)
- (let ((new-result (plist-put (notmuch-search-get-result pos) :tags tags)))
- (notmuch-search-update-result new-result pos)))
+ (notmuch-search-update-result
+ (plist-put (notmuch-search-get-result pos) :tags tags)
+ pos))
(defun notmuch-search-get-tags (&optional pos)
(plist-get (notmuch-search-get-result pos) :tags))
(notmuch-search-foreach-result beg end
(lambda (pos)
(setq output (append output (notmuch-search-get-tags pos)))))
- output))
+ (delete-dups output)))
(defun notmuch-search-interactive-tag-changes (&optional initial-input)
"Prompt for tag changes for the current thread or region.
-Returns (TAG-CHANGES REGION-BEGIN REGION-END)."
+Return (TAG-CHANGES REGION-BEGIN REGION-END)."
(pcase-let ((`(,beg ,end) (notmuch-interactive-region)))
(list (notmuch-read-tag-changes (notmuch-search-get-tags-region beg end)
(if (= beg end) "Tag thread" "Tag region")
(when (eq beg end)
(notmuch-search-next-thread)))
+;;; Search Results
+
(defun notmuch-search-update-result (result &optional pos)
"Replace the result object of the thread at POS (or point) by
RESULT and redraw it.
(min init-point (- new-end 1)))))
(goto-char new-point)))))
-(defun notmuch-search-process-sentinel (proc msg)
+(defun notmuch-search-process-sentinel (proc _msg)
"Add a message to let user know when \"notmuch search\" exits."
(let ((buffer (process-buffer proc))
(status (process-status proc))
(throw 'return nil))
(when (and atbob
(not (string= notmuch-search-target-thread "found")))
- (set 'never-found-target-thread t)))))
+ (setq never-found-target-thread t)))))
(when (and never-found-target-thread
notmuch-search-target-line)
(goto-char (point-min))
(setq invisible-string (notmuch-search-author-propertize invisible-string)))
;; If there is any invisible text, add it as a tooltip to the
;; visible text.
- (unless (string= invisible-string "")
+ (unless (string-empty-p invisible-string)
(setq visible-string
(propertize visible-string
'help-echo (concat "..." invisible-string))))
;; Insert the visible and, if present, invisible author strings.
(insert visible-string)
- (unless (string= invisible-string "")
+ (unless (string-empty-p invisible-string)
(let ((start (point))
overlay)
(insert invisible-string)
"Process and filter the output of \"notmuch search\"."
(let ((results-buf (process-buffer proc))
(parse-buf (process-get proc 'parse-buf))
- (inhibit-read-only t)
- done)
+ (inhibit-read-only t))
(when (buffer-live-p results-buf)
(with-current-buffer parse-buf
;; Insert new data
(notmuch-sexp-parse-partial-list 'notmuch-search-append-result
results-buf)))))
+;;; Commands (and some helper functions used by them)
+
(defun notmuch-search-tag-all (tag-changes)
"Add/remove tags from all messages in current search buffer.
"Read a notmuch-query from the minibuffer with completion.
PROMPT is the string to prompt with."
- (let*
- ((all-tags
- (mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
- (process-lines notmuch-command "search" "--output=tags" "*")))
- (completions
- (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
- "subject:" "attachment:")
- (mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
- (mapcar (lambda (tag) (concat "is:" tag)) all-tags)
- (mapcar (lambda (mimetype) (concat "mimetype:" mimetype))
- (mailcap-mime-types)))))
- (let ((keymap (copy-keymap minibuffer-local-map))
- (current-query (cl-case major-mode
- (notmuch-search-mode (notmuch-search-get-query))
- (notmuch-show-mode (notmuch-show-get-query))
- (notmuch-tree-mode (notmuch-tree-get-query))))
- (minibuffer-completion-table
- (completion-table-dynamic
- (lambda (string)
- ;; generate a list of possible completions for the current input
- (cond
- ;; this ugly regexp is used to get the last word of the input
- ;; possibly preceded by a '('
- ((string-match "\\(^\\|.* (?\\)\\([^ ]*\\)$" string)
- (mapcar (lambda (compl)
- (concat (match-string-no-properties 1 string) compl))
- (all-completions (match-string-no-properties 2 string)
- completions)))
- (t (list string)))))))
- ;; this was simpler than convincing completing-read to accept spaces:
- (define-key keymap (kbd "TAB") 'minibuffer-complete)
- (let ((history-delete-duplicates t))
- (read-from-minibuffer prompt nil keymap nil
- 'notmuch-search-history current-query nil)))))
+ (let* ((all-tags
+ (mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
+ (process-lines notmuch-command "search" "--output=tags" "*")))
+ (completions
+ (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
+ "subject:" "attachment:")
+ (mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
+ (mapcar (lambda (tag) (concat "is:" tag)) all-tags)
+ (mapcar (lambda (mimetype) (concat "mimetype:" mimetype))
+ (mailcap-mime-types))))
+ (keymap (copy-keymap minibuffer-local-map))
+ (current-query (cl-case major-mode
+ (notmuch-search-mode (notmuch-search-get-query))
+ (notmuch-show-mode (notmuch-show-get-query))
+ (notmuch-tree-mode (notmuch-tree-get-query))))
+ (minibuffer-completion-table
+ (completion-table-dynamic
+ (lambda (string)
+ ;; Generate a list of possible completions for the current input.
+ (cond
+ ;; This ugly regexp is used to get the last word of the input
+ ;; possibly preceded by a '('.
+ ((string-match "\\(^\\|.* (?\\)\\([^ ]*\\)$" string)
+ (mapcar (lambda (compl)
+ (concat (match-string-no-properties 1 string) compl))
+ (all-completions (match-string-no-properties 2 string)
+ completions)))
+ (t (list string)))))))
+ ;; This was simpler than convincing completing-read to accept spaces:
+ (define-key keymap (kbd "TAB") 'minibuffer-complete)
+ (let ((history-delete-duplicates t))
+ (read-from-minibuffer prompt nil keymap nil
+ 'notmuch-search-history current-query nil))))
(defun notmuch-search-get-query ()
"Return the current query in this search buffer."
(if no-display
(set-buffer buffer)
(pop-to-buffer-same-window buffer))
- ;; avoid wiping out third party buffer-local variables in the case
- ;; where we're just refreshing or changing the sort order of an
- ;; existing search results buffer
- (unless (eq major-mode 'notmuch-search-mode)
- (notmuch-search-mode))
+ (notmuch-search-mode)
;; Don't track undo information for this buffer
- (set 'buffer-undo-list t)
- (set 'notmuch-search-query-string query)
- (set 'notmuch-search-oldest-first oldest-first)
- (set 'notmuch-search-target-thread target-thread)
- (set 'notmuch-search-target-line target-line)
+ (setq buffer-undo-list t)
+ (setq notmuch-search-query-string query)
+ (setq notmuch-search-oldest-first oldest-first)
+ (setq notmuch-search-target-thread target-thread)
+ (setq notmuch-search-target-line target-line)
(notmuch-tag-clear-cache)
- (let ((proc (get-buffer-process (current-buffer)))
- (inhibit-read-only t))
- (when proc
- (error "notmuch search process already running for query `%s'" query))
+ (when (get-buffer-process buffer)
+ (error "notmuch search process already running for query `%s'" query))
+ (let ((inhibit-read-only t))
(erase-buffer)
(goto-char (point-min))
(save-excursion
(if oldest-first
"--sort=oldest-first"
"--sort=newest-first")
- query))
- ;; Use a scratch buffer to accumulate partial output.
- ;; This buffer will be killed by the sentinel, which
- ;; should be called no matter how the process dies.
- (parse-buf (generate-new-buffer " *notmuch search parse*")))
- (process-put proc 'parse-buf parse-buf)
+ query)))
+ ;; Use a scratch buffer to accumulate partial output.
+ ;; This buffer will be killed by the sentinel, which
+ ;; should be called no matter how the process dies.
+ (process-put proc 'parse-buf
+ (generate-new-buffer " *notmuch search parse*"))
(set-process-filter proc 'notmuch-search-process-filter)
(set-process-query-on-exit-flag proc nil))))
(run-hooks 'notmuch-search-hook)))
thread. Otherwise, point will be moved to attempt to be in the
same relative position within the new buffer."
(interactive)
- (let ((target-line (line-number-at-pos))
- (oldest-first notmuch-search-oldest-first)
- (target-thread (notmuch-search-find-thread-id 'bare))
- (query notmuch-search-query-string))
- ;; notmuch-search erases the current buffer.
- (notmuch-search query oldest-first target-thread target-line t)
- (goto-char (point-min))))
+ (notmuch-search notmuch-search-query-string
+ notmuch-search-oldest-first
+ (notmuch-search-find-thread-id 'bare)
+ (line-number-at-pos)
+ t)
+ (goto-char (point-min)))
(defun notmuch-search-toggle-order ()
"Toggle the current search order.
This command toggles the sort order for the current search. The
default sort order is defined by `notmuch-search-oldest-first'."
(interactive)
- (set 'notmuch-search-oldest-first (not notmuch-search-oldest-first))
+ (setq notmuch-search-oldest-first (not notmuch-search-oldest-first))
(notmuch-search-refresh-view))
(defun notmuch-group-disjunctive-query-string (query-string)
"Group query if it contains a complex expression.
-
-Enclose QUERY-STRING in parentheses if it matches
-`notmuch-search-disjunctive-regexp'."
- (if (string-match-p notmuch-search-disjunctive-regexp query-string)
+Enclose QUERY-STRING in parentheses if contains \"OR\" operators."
+ (if (string-match-p "\\<[oO][rR]\\>" query-string)
(concat "( " query-string " )")
query-string))
notmuch-search-oldest-first)))
(defun notmuch-search-filter-by-tag (tag)
- "Filter the current search results based on a single tag.
+ "Filter the current search results based on a single TAG.
-Runs a new search matching only messages that match both the
-current search results AND that are tagged with the given tag."
+Run a new search matching only messages that match the current
+search results and that are also tagged with the given TAG."
(interactive
(list (notmuch-select-tag-with-completion "Filter by tag: "
notmuch-search-query-string)))
(notmuch-hello))
(defun notmuch-interesting-buffer (b)
- "Is the current buffer of interest to a notmuch user?"
+ "Whether the current buffer's major-mode is a notmuch mode."
(with-current-buffer b
(memq major-mode '(notmuch-show-mode
notmuch-search-mode
(defun notmuch-cycle-notmuch-buffers ()
"Cycle through any existing notmuch buffers (search, show or hello).
-If the current buffer is the only notmuch buffer, bury it. If no
-notmuch buffers exist, run `notmuch'."
+If the current buffer is the only notmuch buffer, bury it.
+If no notmuch buffers exist, run `notmuch'."
(interactive)
(let (start first)
;; If the current buffer is a notmuch buffer, remember it and then
(pop-to-buffer-same-window first))
(notmuch))))
+;;; Integrations
+;;;; Hl-line Support
+
+(defun notmuch-hl-line-mode ()
+ (prog1 (hl-line-mode)
+ (when hl-line-overlay
+ (overlay-put hl-line-overlay 'priority 1))))
+
;;;; Imenu Support
(defun notmuch-search-imenu-prev-index-position-function ()
"Move point to previous message in notmuch-search buffer.
-This function is used as a value for
-`imenu-prev-index-position-function'."
+Used as`imenu-prev-index-position-function' in notmuch buffers."
(notmuch-search-previous-thread))
(defun notmuch-search-imenu-extract-index-name-function ()
"Return imenu name for line at point.
-This function is used as a value for
-`imenu-extract-index-name-function'. Point should be at the
-beginning of the line."
- (let ((subject (notmuch-search-find-subject))
- (author (notmuch-search-find-authors)))
- (format "%s (%s)" subject author)))
+Used as `imenu-extract-index-name-function' in notmuch buffers.
+Point should be at the beginning of the line."
+ (format "%s (%s)"
+ (notmuch-search-find-subject)
+ (notmuch-search-find-authors)))
+
+;;; _
(setq mail-user-agent 'notmuch-user-agent)
-;;; rstdoc.el --- help generate documentation from docstrings -*-lexical-binding: t-*-
+;;; rstdoc.el --- help generate documentation from docstrings -*- lexical-binding: t -*-
;; Copyright (C) 2018 David Bremner
G_BEGIN_DECLS
#define GMIME_TYPE_FILTER_REPLY (g_mime_filter_reply_get_type ())
-#define GMIME_FILTER_REPLY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GMIME_TYPE_FILTER_REPLY, GMimeFilterReply))
-#define GMIME_FILTER_REPLY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GMIME_TYPE_FILTER_REPLY, GMimeFilterReplyClass))
-#define GMIME_IS_FILTER_REPLY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GMIME_TYPE_FILTER_REPLY))
-#define GMIME_IS_FILTER_REPLY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GMIME_TYPE_FILTER_REPLY))
-#define GMIME_FILTER_REPLY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GMIME_TYPE_FILTER_REPLY, GMimeFilterReplyClass))
+#define GMIME_FILTER_REPLY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GMIME_TYPE_FILTER_REPLY, \
+ GMimeFilterReply))
+#define GMIME_FILTER_REPLY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GMIME_TYPE_FILTER_REPLY, \
+ GMimeFilterReplyClass))
+#define GMIME_IS_FILTER_REPLY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GMIME_TYPE_FILTER_REPLY))
+#define GMIME_IS_FILTER_REPLY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GMIME_TYPE_FILTER_REPLY))
+#define GMIME_FILTER_REPLY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GMIME_TYPE_FILTER_REPLY, \
+ GMimeFilterReplyClass))
typedef struct _GMimeFilterReply GMimeFilterReply;
typedef struct _GMimeFilterReplyClass GMimeFilterReplyClass;
#include <sys/wait.h>
int
-notmuch_run_hook (const char *db_path, const char *hook)
+notmuch_run_hook (notmuch_database_t *notmuch, const char *hook)
{
char *hook_path;
int status = 0;
pid_t pid;
- hook_path = talloc_asprintf (NULL, "%s/%s/%s/%s", db_path, ".notmuch",
- "hooks", hook);
+ hook_path = talloc_asprintf (notmuch, "%s/%s",
+ notmuch_config_get (notmuch, NOTMUCH_CONFIG_HOOK_DIR),
+ hook);
if (hook_path == NULL) {
fprintf (stderr, "Out of memory\n");
return 1;
$(dir)/config.cc \
$(dir)/regexp-fields.cc \
$(dir)/thread.cc \
- $(dir)/thread-fp.cc
+ $(dir)/thread-fp.cc \
+ $(dir)/features.cc \
+ $(dir)/prefix.cc \
+ $(dir)/open.cc
libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
goto DONE;
}
- _notmuch_message_add_filename (message, filename);
+ ret = _notmuch_message_add_filename (message, filename);
+ if (ret)
+ goto DONE;
if (is_new || is_ghost) {
_notmuch_message_add_term (message, "type", "mail");
#include "notmuch-private.h"
#include "database-private.h"
+#include <pwd.h>
+#include <netdb.h>
+
static const std::string CONFIG_PREFIX = "C";
struct _notmuch_config_list {
char *current_val;
};
+struct _notmuch_config_values {
+ const char *iterator;
+ size_t tok_len;
+ const char *string;
+ void *children; /* talloc_context */
+};
+
+struct _notmuch_config_pairs {
+ notmuch_string_map_iterator_t *iter;
+};
+
+static const char *_notmuch_config_key_to_string (notmuch_config_key_t key);
+static char *_expand_path (void *ctx, const char *key, const char *val);
+
static int
_notmuch_config_list_destroy (notmuch_config_list_t *list)
{
if (status)
return status;
+ if (! notmuch->config) {
+ if ((status = _notmuch_config_load_from_database (notmuch)))
+ return status;
+ }
+
try {
notmuch->writable_xapian_db->set_metadata (CONFIG_PREFIX + key, value);
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
error.get_msg ().c_str ());
}
- return status;
+
+ if (status)
+ return status;
+
+ _notmuch_string_map_set (notmuch->config, key, value);
+
+ return NOTMUCH_STATUS_SUCCESS;
}
static notmuch_status_t
const char *key,
char **value)
{
- std::string strval;
+ const char *stored_val;
notmuch_status_t status;
+ if (! notmuch->config) {
+ if ((status = _notmuch_config_load_from_database (notmuch)))
+ return status;
+ }
+
if (! value)
return NOTMUCH_STATUS_NULL_POINTER;
- status = _metadata_value (notmuch, key, strval);
- if (status)
- return status;
-
- *value = strdup (strval.c_str ());
+ stored_val = _notmuch_string_map_get (notmuch->config, key);
+ if (! stored_val) {
+ /* XXX in principle this API should be fixed so empty string
+ * is distinguished from not found */
+ *value = strdup ("");
+ } else {
+ *value = strdup (stored_val);
+ }
return NOTMUCH_STATUS_SUCCESS;
}
talloc_set_destructor (list, _notmuch_config_list_destroy);
} catch (const Xapian::Error &error) {
- _notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n",
+ _notmuch_database_log (notmuch,
+ "A Xapian exception occurred getting metadata iterator: %s.\n",
error.get_msg ().c_str ());
notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
if (status != NOTMUCH_STATUS_XAPIAN_EXCEPTION)
_notmuch_config_list_destroy (list);
}
- } else {
+ } else {
talloc_set_destructor (list, _notmuch_config_list_destroy);
}
return true;
}
-static inline char * _key_from_iterator (notmuch_config_list_t *list) {
+static inline char *
+_key_from_iterator (notmuch_config_list_t *list)
+{
return talloc_strdup (list, (*list->iterator).c_str () + CONFIG_PREFIX.length ());
}
{
talloc_free (list);
}
+
+notmuch_status_t
+_notmuch_config_load_from_database (notmuch_database_t *notmuch)
+{
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+ notmuch_config_list_t *list;
+
+ if (notmuch->config == NULL)
+ notmuch->config = _notmuch_string_map_create (notmuch);
+
+ if (unlikely (notmuch->config == NULL))
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ status = notmuch_database_get_config_list (notmuch, "", &list);
+ if (status)
+ return status;
+
+ 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));
+ _notmuch_string_map_append (notmuch->config, key, normalized_val);
+ talloc_free (normalized_val);
+ }
+
+ return status;
+}
+
+notmuch_config_values_t *
+notmuch_config_get_values (notmuch_database_t *notmuch, notmuch_config_key_t key)
+{
+ const char *key_str = _notmuch_config_key_to_string (key);
+
+ if (! key_str)
+ return NULL;
+
+ return notmuch_config_get_values_string (notmuch, key_str);
+}
+
+notmuch_config_values_t *
+notmuch_config_get_values_string (notmuch_database_t *notmuch, const char *key_str)
+{
+ notmuch_config_values_t *values = NULL;
+ bool ok = false;
+
+ values = talloc (notmuch, notmuch_config_values_t);
+ if (unlikely (! values))
+ goto DONE;
+
+ values->children = talloc_new (values);
+
+ values->string = _notmuch_string_map_get (notmuch->config, key_str);
+ if (! values->string)
+ goto DONE;
+
+ values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
+ ok = true;
+
+ DONE:
+ if (! ok) {
+ if (values)
+ talloc_free (values);
+ return NULL;
+ }
+ return values;
+}
+
+notmuch_bool_t
+notmuch_config_values_valid (notmuch_config_values_t *values)
+{
+ if (! values)
+ return false;
+
+ return (values->iterator != NULL);
+}
+
+const char *
+notmuch_config_values_get (notmuch_config_values_t *values)
+{
+ return talloc_strndup (values->children, values->iterator, values->tok_len);
+}
+
+void
+notmuch_config_values_start (notmuch_config_values_t *values)
+{
+ if (values == NULL)
+ return;
+ if (values->children) {
+ talloc_free (values->children);
+ }
+
+ values->children = talloc_new (values);
+
+ values->iterator = strsplit_len (values->string, ';', &(values->tok_len));
+}
+
+void
+notmuch_config_values_move_to_next (notmuch_config_values_t *values)
+{
+ values->iterator += values->tok_len;
+ values->iterator = strsplit_len (values->iterator, ';', &(values->tok_len));
+}
+
+void
+notmuch_config_values_destroy (notmuch_config_values_t *values)
+{
+ talloc_free (values);
+}
+
+notmuch_config_pairs_t *
+notmuch_config_get_pairs (notmuch_database_t *notmuch,
+ const char *prefix)
+{
+ notmuch_config_pairs_t *pairs = talloc (notmuch, notmuch_config_pairs_t);
+
+ pairs->iter = _notmuch_string_map_iterator_create (notmuch->config, prefix, false);
+ return pairs;
+}
+
+notmuch_bool_t
+notmuch_config_pairs_valid (notmuch_config_pairs_t *pairs)
+{
+ return _notmuch_string_map_iterator_valid (pairs->iter);
+}
+
+void
+notmuch_config_pairs_move_to_next (notmuch_config_pairs_t *pairs)
+{
+ _notmuch_string_map_iterator_move_to_next (pairs->iter);
+}
+
+const char *
+notmuch_config_pairs_key (notmuch_config_pairs_t *pairs)
+{
+ return _notmuch_string_map_iterator_key (pairs->iter);
+}
+
+const char *
+notmuch_config_pairs_value (notmuch_config_pairs_t *pairs)
+{
+ return _notmuch_string_map_iterator_value (pairs->iter);
+}
+
+void
+notmuch_config_pairs_destroy (notmuch_config_pairs_t *pairs)
+{
+ _notmuch_string_map_iterator_destroy (pairs->iter);
+ talloc_free (pairs);
+}
+
+static char *
+_expand_path (void *ctx, const char *key, const char *val)
+{
+ char *expanded_val;
+
+ if ((strcmp (key, "database.path") == 0 ||
+ strcmp (key, "database.mail_root") == 0 ||
+ strcmp (key, "database.hook_dir") == 0 ||
+ strcmp (key, "database.backup_path") == 0 ) &&
+ val[0] != '/')
+ expanded_val = talloc_asprintf (ctx, "%s/%s", getenv ("HOME"), val);
+ else
+ expanded_val = talloc_strdup (ctx, val);
+
+ return expanded_val;
+}
+
+notmuch_status_t
+_notmuch_config_load_from_file (notmuch_database_t *notmuch,
+ GKeyFile *file)
+{
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+ gchar **groups = NULL, **keys, *val;
+
+ if (notmuch->config == NULL)
+ notmuch->config = _notmuch_string_map_create (notmuch);
+
+ if (unlikely (notmuch->config == NULL)) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ groups = g_key_file_get_groups (file, NULL);
+ for (gchar **grp = groups; *grp; grp++) {
+ keys = g_key_file_get_keys (file, *grp, NULL, NULL);
+ for (gchar **keys_p = keys; *keys_p; keys_p++) {
+ char *absolute_key = talloc_asprintf (notmuch, "%s.%s", *grp, *keys_p);
+ char *normalized_val;
+ val = g_key_file_get_value (file, *grp, *keys_p, NULL);
+ if (! val) {
+ status = NOTMUCH_STATUS_FILE_ERROR;
+ goto DONE;
+ }
+ normalized_val = _expand_path (notmuch, absolute_key, val);
+ _notmuch_string_map_set (notmuch->config, absolute_key, normalized_val);
+ g_free (val);
+ talloc_free (absolute_key);
+ talloc_free (normalized_val);
+ if (status)
+ goto DONE;
+ }
+ g_strfreev (keys);
+ }
+
+ DONE:
+ if (groups)
+ g_strfreev (groups);
+
+ return status;
+}
+
+notmuch_status_t
+notmuch_config_get_bool (notmuch_database_t *notmuch, notmuch_config_key_t key, notmuch_bool_t *val)
+{
+ const char *key_string, *val_string;
+
+ key_string = _notmuch_config_key_to_string (key);
+ if (! key_string) {
+ return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
+ }
+
+ val_string = _notmuch_string_map_get (notmuch->config, key_string);
+ if (! val_string) {
+ *val = FALSE;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ if (strcase_equal (val_string, "false") || strcase_equal (val_string, "no"))
+ *val = FALSE;
+ else if (strcase_equal (val_string, "true") || strcase_equal (val_string, "yes"))
+ *val = TRUE;
+ else
+ return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static const char *
+_get_name_from_passwd_file (void *ctx)
+{
+ long pw_buf_size;
+ char *pw_buf;
+ struct passwd passwd, *ignored;
+ const char *name;
+ int e;
+
+ pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
+ if (pw_buf_size == -1) pw_buf_size = 64;
+ pw_buf = (char *) talloc_size (ctx, pw_buf_size);
+
+ while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
+ pw_buf_size, &ignored)) == ERANGE) {
+ pw_buf_size = pw_buf_size * 2;
+ pw_buf = (char *) talloc_zero_size (ctx, pw_buf_size);
+ }
+
+ if (e == 0) {
+ char *comma = strchr (passwd.pw_gecos, ',');
+ if (comma)
+ name = talloc_strndup (ctx, passwd.pw_gecos,
+ comma - passwd.pw_gecos);
+ else
+ name = talloc_strdup (ctx, passwd.pw_gecos);
+ } else {
+ name = talloc_strdup (ctx, "");
+ }
+
+ talloc_free (pw_buf);
+
+ return name;
+}
+
+static char *
+_get_username_from_passwd_file (void *ctx)
+{
+ long pw_buf_size;
+ char *pw_buf;
+ struct passwd passwd, *ignored;
+ char *name;
+ int e;
+
+ pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
+ if (pw_buf_size == -1) pw_buf_size = 64;
+ pw_buf = (char *) talloc_zero_size (ctx, pw_buf_size);
+
+ while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
+ pw_buf_size, &ignored)) == ERANGE) {
+ pw_buf_size = pw_buf_size * 2;
+ pw_buf = (char *) talloc_zero_size (ctx, pw_buf_size);
+ }
+
+ if (e == 0)
+ name = talloc_strdup (ctx, passwd.pw_name);
+ else
+ name = talloc_strdup (ctx, "");
+
+ talloc_free (pw_buf);
+
+ return name;
+}
+
+static const char *
+_get_email_from_passwd_file (void *ctx)
+{
+
+ char hostname[256];
+ struct hostent *hostent;
+ const char *domainname;
+ char *email;
+
+ char *username = _get_username_from_passwd_file (ctx);
+
+ gethostname (hostname, 256);
+ hostname[255] = '\0';
+
+ hostent = gethostbyname (hostname);
+ if (hostent && (domainname = strchr (hostent->h_name, '.')))
+ domainname += 1;
+ else
+ domainname = "(none)";
+
+ email = talloc_asprintf (ctx, "%s@%s.%s",
+ username, hostname, domainname);
+
+ talloc_free (username);
+ return email;
+}
+
+static const char *
+_notmuch_config_key_to_string (notmuch_config_key_t key)
+{
+ switch (key) {
+ case NOTMUCH_CONFIG_DATABASE_PATH:
+ return "database.path";
+ case NOTMUCH_CONFIG_MAIL_ROOT:
+ return "database.mail_root";
+ case NOTMUCH_CONFIG_HOOK_DIR:
+ return "database.hook_dir";
+ case NOTMUCH_CONFIG_BACKUP_DIR:
+ return "database.backup_dir";
+ case NOTMUCH_CONFIG_EXCLUDE_TAGS:
+ return "search.exclude_tags";
+ case NOTMUCH_CONFIG_NEW_TAGS:
+ return "new.tags";
+ case NOTMUCH_CONFIG_NEW_IGNORE:
+ return "new.ignore";
+ case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
+ return "maildir.synchronize_flags";
+ case NOTMUCH_CONFIG_PRIMARY_EMAIL:
+ return "user.primary_email";
+ case NOTMUCH_CONFIG_OTHER_EMAIL:
+ return "user.other_email";
+ case NOTMUCH_CONFIG_USER_NAME:
+ return "user.name";
+ default:
+ return NULL;
+ }
+}
+
+static const char *
+_notmuch_config_default (notmuch_database_t *notmuch, notmuch_config_key_t key)
+{
+ char *path;
+ const char *name, *email;
+
+ switch (key) {
+ case NOTMUCH_CONFIG_DATABASE_PATH:
+ path = getenv ("MAILDIR");
+ if (path)
+ path = talloc_strdup (notmuch, path);
+ else
+ path = talloc_asprintf (notmuch, "%s/mail",
+ getenv ("HOME"));
+ return path;
+ case NOTMUCH_CONFIG_MAIL_ROOT:
+ /* by default, mail root is the same as database path */
+ return notmuch_database_get_path (notmuch);
+ case NOTMUCH_CONFIG_EXCLUDE_TAGS:
+ return "";
+ case NOTMUCH_CONFIG_NEW_TAGS:
+ return "unread;inbox";
+ case NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS:
+ return "true";
+ case NOTMUCH_CONFIG_USER_NAME:
+ name = getenv ("NAME");
+ if (name)
+ name = talloc_strdup (notmuch, name);
+ else
+ name = _get_name_from_passwd_file (notmuch);
+ return name;
+ case NOTMUCH_CONFIG_PRIMARY_EMAIL:
+ email = getenv ("EMAIL");
+ if (email)
+ email = talloc_strdup (notmuch, email);
+ else
+ email = _get_email_from_passwd_file (notmuch);
+ return email;
+ case NOTMUCH_CONFIG_NEW_IGNORE:
+ return "";
+ case NOTMUCH_CONFIG_HOOK_DIR:
+ case NOTMUCH_CONFIG_BACKUP_DIR:
+ case NOTMUCH_CONFIG_OTHER_EMAIL:
+ return NULL;
+ default:
+ case NOTMUCH_CONFIG_LAST:
+ INTERNAL_ERROR ("illegal key enum %d", key);
+ }
+}
+
+notmuch_status_t
+_notmuch_config_load_defaults (notmuch_database_t *notmuch)
+{
+ notmuch_config_key_t key;
+
+ for (key = NOTMUCH_CONFIG_FIRST;
+ key < NOTMUCH_CONFIG_LAST;
+ key = notmuch_config_key_t (key + 1)) {
+ const char *val = notmuch_config_get (notmuch, key);
+ const char *key_string = _notmuch_config_key_to_string (key);
+
+ val = _notmuch_string_map_get (notmuch->config, key_string);
+ if (! val) {
+ _notmuch_string_map_set (notmuch->config, key_string, _notmuch_config_default (notmuch,
+ key));
+ }
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+const char *
+notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key)
+{
+
+ return _notmuch_string_map_get (notmuch->config, _notmuch_config_key_to_string (key));
+}
+
+const char *
+notmuch_config_path (notmuch_database_t *notmuch)
+{
+ return notmuch->config_path;
+}
+
+notmuch_status_t
+notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
+{
+
+ return notmuch_database_set_config (notmuch, _notmuch_config_key_to_string (key), val);
+}
+
+void
+_notmuch_config_cache (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val)
+{
+ if (notmuch->config == NULL)
+ notmuch->config = _notmuch_string_map_create (notmuch);
+
+ _notmuch_string_map_set (notmuch->config, _notmuch_config_key_to_string (key), val);
+}
#include "notmuch-private.h"
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
#ifdef SILENCE_XAPIAN_DEPRECATION_WARNINGS
#define XAPIAN_DEPRECATED(D) D
#endif
struct _notmuch_database {
bool exception_reported;
- char *path;
+ /* Path to actual database */
+ const char *xapian_path;
+
+ /* Path to config loaded, if any */
+ const char *config_path;
int atomic_nesting;
/* true if changes have been made in this atomic section */
* here, but at least they are small */
notmuch_string_map_t *user_prefix;
notmuch_string_map_t *user_header;
+
+ /* Cached and possibly overridden configuration */
+ notmuch_string_map_t *config;
};
/* Prior to database version 3, features were implied by the database
const char *value,
Xapian::PostingIterator *begin,
Xapian::PostingIterator *end);
+
+#define NOTMUCH_DATABASE_VERSION 3
+
+/* features.cc */
+
+_notmuch_features
+_notmuch_database_parse_features (const void *ctx, const char *features, unsigned int version,
+ char mode, char **incompat_out);
+
+char *
+_notmuch_database_print_features (const void *ctx, unsigned int features);
+
+/* prefix.cc */
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch);
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch);
+
#endif
*/
#include "database-private.h"
-#include "parse-time-vrp.h"
-#include "query-fp.h"
-#include "thread-fp.h"
-#include "regexp-fields.h"
#include "string-util.h"
#include <iostream>
using namespace std;
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
typedef struct {
const char *name;
const char *prefix;
#define STRINGIFY(s) _SUB_STRINGIFY (s)
#define _SUB_STRINGIFY(s) #s
-#if HAVE_XAPIAN_DB_RETRY_LOCK
-#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
-#else
-#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
-#endif
-
#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
static void
-_log_xapian_exception (const char *where, notmuch_database_t *notmuch, const Xapian::Error error) {
+_log_xapian_exception (const char *where, notmuch_database_t *notmuch, const Xapian::Error error)
+{
_notmuch_database_log (notmuch,
"A Xapian exception occurred at %s: %s\n",
where,
* same thread.
*/
-/* With these prefix values we follow the conventions published here:
- *
- * https://xapian.org/docs/omega/termprefixes.html
- *
- * as much as makes sense. Note that I took some liberty in matching
- * the reserved prefix values to notmuch concepts, (for example, 'G'
- * is documented as "newsGroup (or similar entity - e.g. a web forum
- * name)", for which I think the thread is the closest analogue in
- * notmuch. This in spite of the fact that we will eventually be
- * storing mailing-list messages where 'G' for "mailing list name"
- * might be even a closer analogue. I'm treating the single-character
- * prefixes preferentially for core notmuch concepts (which will be
- * nearly universal to all mail messages).
- */
-
-static const
-prefix_t prefix_table[] = {
- /* name term prefix flags */
- { "type", "T", NOTMUCH_FIELD_NO_FLAGS },
- { "reference", "XREFERENCE", NOTMUCH_FIELD_NO_FLAGS },
- { "replyto", "XREPLYTO", NOTMUCH_FIELD_NO_FLAGS },
- { "directory", "XDIRECTORY", NOTMUCH_FIELD_NO_FLAGS },
- { "file-direntry", "XFDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
- { "directory-direntry", "XDDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
- { "body", "", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "thread", "G", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "tag", "K", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "is", "K", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "id", "Q", NOTMUCH_FIELD_EXTERNAL },
- { "mid", "Q", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "path", "P", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL },
- /*
- * Unconditionally add ':' to reduce potential ambiguity with
- * overlapping prefixes and/or terms that start with capital
- * letters. See Xapian document termprefixes.html for related
- * discussion.
- */
- { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "date", NULL, NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "query", NULL, NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROCESSOR },
- { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC |
- NOTMUCH_FIELD_PROCESSOR },
- { "to", "XTO", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "attachment", "XATTACHMENT", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "mimetype", "XMIMETYPE", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC },
- { "subject", "XSUBJECT", NOTMUCH_FIELD_EXTERNAL |
- NOTMUCH_FIELD_PROBABILISTIC |
- NOTMUCH_FIELD_PROCESSOR },
-};
-
-static void
-_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
- if (prefix->prefix)
- notmuch->query_parser->add_prefix ("", prefix->prefix);
- if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
- notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
- else
- notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
-}
notmuch_string_map_iterator_t *
_notmuch_database_user_headers (notmuch_database_t *notmuch)
return _notmuch_string_map_iterator_create (notmuch->user_header, "", false);
}
-const char *
-_user_prefix (void *ctx, const char *name)
-{
- return talloc_asprintf (ctx, "XU%s:", name);
-}
-
-static notmuch_status_t
-_setup_user_query_fields (notmuch_database_t *notmuch)
-{
- notmuch_config_list_t *list;
- notmuch_status_t status;
-
- notmuch->user_prefix = _notmuch_string_map_create (notmuch);
- if (notmuch->user_prefix == NULL)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
- notmuch->user_header = _notmuch_string_map_create (notmuch);
- if (notmuch->user_header == NULL)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
- status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
- if (status)
- return status;
-
- for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
-
- prefix_t query_field;
-
- const char *key = notmuch_config_list_key (list)
- + sizeof (CONFIG_HEADER_PREFIX) - 1;
-
- _notmuch_string_map_append (notmuch->user_prefix,
- key,
- _user_prefix (notmuch, key));
-
- _notmuch_string_map_append (notmuch->user_header,
- key,
- notmuch_config_list_value (list));
-
- query_field.name = talloc_strdup (notmuch, key);
- query_field.prefix = _user_prefix (notmuch, key);
- query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
- | NOTMUCH_FIELD_EXTERNAL;
-
- _setup_query_field_default (&query_field, notmuch);
- }
-
- notmuch_config_list_destroy (list);
-
- return NOTMUCH_STATUS_SUCCESS;
-}
-
-static void
-_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
- if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
- Xapian::FieldProcessor *fp;
-
- if (STRNCMP_LITERAL (prefix->name, "date") == 0)
- fp = (new DateFieldProcessor(NOTMUCH_VALUE_TIMESTAMP))->release ();
- else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
- fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
- else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
- fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
- else
- fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
- *notmuch->query_parser, notmuch))->release ();
-
- /* we treat all field-processor fields as boolean in order to get the raw input */
- if (prefix->prefix)
- notmuch->query_parser->add_prefix ("", prefix->prefix);
- notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
- } else {
- _setup_query_field_default (prefix, notmuch);
- }
-}
-
-const char *
-_find_prefix (const char *name)
-{
- unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- if (strcmp (name, prefix_table[i].name) == 0)
- return prefix_table[i].prefix;
- }
-
- INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
-
- return "";
-}
-
-/* Like find prefix, but include the possibility of user defined
- * prefixes specific to this database */
-
-const char *
-_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
-{
- unsigned int i;
-
- /*XXX TODO: reduce code duplication */
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- if (strcmp (name, prefix_table[i].name) == 0)
- return prefix_table[i].prefix;
- }
-
- if (notmuch->user_prefix)
- return _notmuch_string_map_get (notmuch->user_prefix, name);
-
- return NULL;
-}
-
-static const struct {
- /* NOTMUCH_FEATURE_* value. */
- _notmuch_features value;
- /* Feature name as it appears in the database. This name should
- * be appropriate for displaying to the user if an older version
- * of notmuch doesn't support this feature. */
- const char *name;
- /* Compatibility flags when this feature is declared. */
- const char *flags;
-} feature_names[] = {
- { NOTMUCH_FEATURE_FILE_TERMS,
- "multiple paths per message", "rw" },
- { NOTMUCH_FEATURE_DIRECTORY_DOCS,
- "relative directory paths", "rw" },
- /* Header values are not required for reading a database because a
- * reader can just refer to the message file. */
- { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
- "from/subject/message-ID in database", "w" },
- { NOTMUCH_FEATURE_BOOL_FOLDER,
- "exact folder:/path: search", "rw" },
- { NOTMUCH_FEATURE_GHOSTS,
- "mail documents for missing messages", "w" },
- /* Knowledge of the index mime-types are not required for reading
- * a database because a reader will just be unable to query
- * them. */
- { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
- "indexed MIME types", "w" },
- { NOTMUCH_FEATURE_LAST_MOD,
- "modification tracking", "w" },
- /* Existing databases will work fine for all queries not involving
- * 'body:' */
- { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
- "index body and headers separately", "w" },
-};
-
const char *
notmuch_status_to_string (notmuch_status_t status)
{
return "Operation requires a database upgrade";
case NOTMUCH_STATUS_PATH_ERROR:
return "Path supplied is illegal for this function";
+ case NOTMUCH_STATUS_IGNORED:
+ return "Argument was ignored";
+ case NOTMUCH_STATUS_ILLEGAL_ARGUMENT:
+ return "Illegal argument for function";
case NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL:
return "Crypto protocol missing, malformed, or unintelligible";
case NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION:
return "Crypto engine initialization failure";
case NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL:
return "Unknown crypto protocol";
+ case NOTMUCH_STATUS_NO_CONFIG:
+ return "No configuration file found";
+ case NOTMUCH_STATUS_NO_DATABASE:
+ return "No database found";
+ case NOTMUCH_STATUS_DATABASE_EXISTS:
+ return "Database exists, not recreated";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
}
}
-notmuch_status_t
-notmuch_database_create (const char *path, notmuch_database_t **database)
-{
- char *status_string = NULL;
- notmuch_status_t status;
-
- status = notmuch_database_create_verbose (path, database,
- &status_string);
-
- if (status_string) {
- fputs (status_string, stderr);
- free (status_string);
- }
-
- return status;
-}
-
-notmuch_status_t
-notmuch_database_create_verbose (const char *path,
- notmuch_database_t **database,
- char **status_string)
-{
- notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
- notmuch_database_t *notmuch = NULL;
- char *notmuch_path = NULL;
- char *message = NULL;
- struct stat st;
- int err;
-
- if (path == NULL) {
- message = strdup ("Error: Cannot create a database for a NULL path.\n");
- status = NOTMUCH_STATUS_NULL_POINTER;
- goto DONE;
- }
-
- if (path[0] != '/') {
- message = strdup ("Error: Database path must be absolute.\n");
- status = NOTMUCH_STATUS_PATH_ERROR;
- goto DONE;
- }
-
- err = stat (path, &st);
- if (err) {
- IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
- path, strerror (errno)));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- if (! S_ISDIR (st.st_mode)) {
- IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
- "Not a directory.\n",
- path));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
-
- err = mkdir (notmuch_path, 0755);
-
- if (err) {
- IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
- notmuch_path, strerror (errno)));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- status = notmuch_database_open_verbose (path,
- NOTMUCH_DATABASE_MODE_READ_WRITE,
- ¬much, &message);
- if (status)
- goto DONE;
-
- /* Upgrade doesn't add these feature to existing databases, but
- * new databases have them. */
- notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
- notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
- notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
-
- status = notmuch_database_upgrade (notmuch, NULL, NULL);
- if (status) {
- notmuch_database_close (notmuch);
- notmuch = NULL;
- }
-
- DONE:
- if (notmuch_path)
- talloc_free (notmuch_path);
-
- if (message) {
- if (status_string)
- *status_string = message;
- else
- free (message);
- }
- if (database)
- *database = notmuch;
- else
- talloc_free (notmuch);
- return status;
-}
-
notmuch_status_t
_notmuch_database_ensure_writable (notmuch_database_t *notmuch)
{
return new_revision;
}
-/* Parse a database features string from the given database version.
- * Returns the feature bit set.
- *
- * For version < 3, this ignores the features string and returns a
- * hard-coded set of features.
- *
- * If there are unrecognized features that are required to open the
- * database in mode (which should be 'r' or 'w'), return a
- * comma-separated list of unrecognized but required features in
- * *incompat_out suitable for presenting to the user. *incompat_out
- * will be allocated from ctx.
- */
-static _notmuch_features
-_parse_features (const void *ctx, const char *features, unsigned int version,
- char mode, char **incompat_out)
-{
- _notmuch_features res = static_cast<_notmuch_features>(0);
- unsigned int namelen, i;
- size_t llen = 0;
- const char *flags;
-
- /* Prior to database version 3, features were implied by the
- * version number. */
- if (version == 0)
- return NOTMUCH_FEATURES_V0;
- else if (version == 1)
- return NOTMUCH_FEATURES_V1;
- else if (version == 2)
- return NOTMUCH_FEATURES_V2;
-
- /* Parse the features string */
- while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
- flags = strchr (features, '\t');
- if (! flags || flags > features + llen)
- continue;
- namelen = flags - features;
-
- for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
- if (strlen (feature_names[i].name) == namelen &&
- strncmp (feature_names[i].name, features, namelen) == 0) {
- res |= feature_names[i].value;
- break;
- }
- }
-
- if (i == ARRAY_SIZE (feature_names) && incompat_out) {
- /* Unrecognized feature */
- const char *have = strchr (flags, mode);
- if (have && have < features + llen) {
- /* This feature is required to access this database in
- * 'mode', but we don't understand it. */
- if (! *incompat_out)
- *incompat_out = talloc_strdup (ctx, "");
- *incompat_out = talloc_asprintf_append_buffer (
- *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
- namelen, features);
- }
- }
- }
-
- return res;
-}
-
-static char *
-_print_features (const void *ctx, unsigned int features)
-{
- unsigned int i;
- char *res = talloc_strdup (ctx, "");
-
- for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
- if (features & feature_names[i].value)
- res = talloc_asprintf_append_buffer (
- res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
-
- return res;
-}
-
-notmuch_status_t
-notmuch_database_open (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database)
-{
- char *status_string = NULL;
- notmuch_status_t status;
-
- status = notmuch_database_open_verbose (path, mode, database,
- &status_string);
-
- if (status_string) {
- fputs (status_string, stderr);
- free (status_string);
- }
-
- return status;
-}
-
-notmuch_status_t
-notmuch_database_open_verbose (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database,
- char **status_string)
-{
- notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
- void *local = talloc_new (NULL);
- notmuch_database_t *notmuch = NULL;
- char *notmuch_path, *xapian_path, *incompat_features;
- char *message = NULL;
- struct stat st;
- int err;
- unsigned int i, version;
- static int initialized = 0;
-
- if (path == NULL) {
- message = strdup ("Error: Cannot open a database for a NULL path.\n");
- status = NOTMUCH_STATUS_NULL_POINTER;
- goto DONE;
- }
-
- if (path[0] != '/') {
- message = strdup ("Error: Database path must be absolute.\n");
- status = NOTMUCH_STATUS_PATH_ERROR;
- goto DONE;
- }
-
- if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
- message = strdup ("Out of memory\n");
- status = NOTMUCH_STATUS_OUT_OF_MEMORY;
- goto DONE;
- }
-
- err = stat (notmuch_path, &st);
- if (err) {
- IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
- notmuch_path, strerror (errno)));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
- message = strdup ("Out of memory\n");
- status = NOTMUCH_STATUS_OUT_OF_MEMORY;
- goto DONE;
- }
-
- /* Initialize the GLib type system and threads */
-#if ! GLIB_CHECK_VERSION (2, 35, 1)
- g_type_init ();
-#endif
-
- /* Initialize gmime */
- if (! initialized) {
- g_mime_init ();
- initialized = 1;
- }
-
- notmuch = talloc_zero (NULL, notmuch_database_t);
- notmuch->exception_reported = false;
- notmuch->status_string = NULL;
- notmuch->path = talloc_strdup (notmuch, path);
-
- strip_trailing (notmuch->path, '/');
-
- notmuch->writable_xapian_db = NULL;
- notmuch->atomic_nesting = 0;
- notmuch->view = 1;
- try {
- string last_thread_id;
- string last_mod;
-
- if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
- notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
- DB_ACTION);
- notmuch->xapian_db = notmuch->writable_xapian_db;
- } else {
- notmuch->xapian_db = new Xapian::Database (xapian_path);
- }
-
- /* Check version. As of database version 3, we represent
- * changes in terms of features, so assume a version bump
- * means a dramatically incompatible change. */
- version = notmuch_database_get_version (notmuch);
- if (version > NOTMUCH_DATABASE_VERSION) {
- IGNORE_RESULT (asprintf (&message,
- "Error: Notmuch database at %s\n"
- " has a newer database format version (%u) than supported by this\n"
- " version of notmuch (%u).\n",
- notmuch_path, version, NOTMUCH_DATABASE_VERSION));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- /* Check features. */
- incompat_features = NULL;
- notmuch->features = _parse_features (
- local, notmuch->xapian_db->get_metadata ("features").c_str (),
- version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
- &incompat_features);
- if (incompat_features) {
- IGNORE_RESULT (asprintf (&message,
- "Error: Notmuch database at %s\n"
- " requires features (%s)\n"
- " not supported by this version of notmuch.\n",
- notmuch_path, incompat_features));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
- last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
- if (last_thread_id.empty ()) {
- notmuch->last_thread_id = 0;
- } else {
- const char *str;
- char *end;
-
- str = last_thread_id.c_str ();
- notmuch->last_thread_id = strtoull (str, &end, 16);
- if (*end != '\0')
- INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
- }
-
- /* Get current highest revision number. */
- last_mod = notmuch->xapian_db->get_value_upper_bound (
- NOTMUCH_VALUE_LAST_MOD);
- if (last_mod.empty ())
- notmuch->revision = 0;
- else
- notmuch->revision = Xapian::sortable_unserialise (last_mod);
- notmuch->uuid = talloc_strdup (
- notmuch, notmuch->xapian_db->get_uuid ().c_str ());
-
- notmuch->query_parser = new Xapian::QueryParser;
- notmuch->term_gen = new Xapian::TermGenerator;
- notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
- notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
- notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
- notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
- notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
- notmuch->query_parser->set_database (*notmuch->xapian_db);
- notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
- notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
- notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
- notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
- notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
-
- for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
- const prefix_t *prefix = &prefix_table[i];
- if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
- _setup_query_field (prefix, notmuch);
- }
- }
- status = _setup_user_query_fields (notmuch);
- } catch (const Xapian::Error &error) {
- IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
- error.get_msg ().c_str ()));
- notmuch_database_destroy (notmuch);
- notmuch = NULL;
- status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
- }
-
- DONE:
- talloc_free (local);
-
- if (message) {
- if (status_string)
- *status_string = message;
- else
- free (message);
- }
-
- if (database)
- *database = notmuch;
- else
- talloc_free (notmuch);
-
- if (notmuch)
- notmuch->open = true;
-
- return status;
-}
-
notmuch_status_t
notmuch_database_close (notmuch_database_t *notmuch)
{
} catch (const Xapian::Error &error) {
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
if (! notmuch->exception_reported) {
- _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n",
+ _notmuch_database_log (notmuch,
+ "Error: A Xapian exception occurred closing database: %s\n",
error.get_msg ().c_str ());
}
}
return status;
}
-notmuch_status_t
-_notmuch_database_reopen (notmuch_database_t *notmuch)
-{
- if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_ONLY)
- return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
-
- try {
- notmuch->xapian_db->reopen ();
- } catch (const Xapian::Error &error) {
- if (! notmuch->exception_reported) {
- _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
- error.get_msg ().c_str ());
- notmuch->exception_reported = true;
- }
- return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
- }
-
- notmuch->view++;
-
- return NOTMUCH_STATUS_SUCCESS;
-}
-
static int
unlink_cb (const char *path,
unused (const struct stat *sb),
notmuch_compact_status_cb_t status_cb,
void *closure)
{
- void *local;
- char *notmuch_path, *xapian_path, *compact_xapian_path;
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
notmuch_database_t *notmuch = NULL;
- struct stat statbuf;
- bool keep_backup;
char *message = NULL;
- local = talloc_new (NULL);
- if (! local)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
ret = notmuch_database_open_verbose (path,
NOTMUCH_DATABASE_MODE_READ_WRITE,
¬much,
&message);
if (ret) {
if (status_cb) status_cb (message, closure);
- goto DONE;
+ return ret;
}
- if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
- ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
- goto DONE;
- }
+ _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
- if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
- ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ return notmuch_database_compact_db (notmuch,
+ backup_path,
+ status_cb,
+ closure);
+}
+
+notmuch_status_t
+notmuch_database_compact_db (notmuch_database_t *notmuch,
+ const char *backup_path,
+ notmuch_compact_status_cb_t status_cb,
+ void *closure)
+{
+ void *local;
+ const char *xapian_path, *compact_xapian_path;
+ const char *path;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ struct stat statbuf;
+ bool keep_backup;
+ char *message;
+
+ ret = _notmuch_database_ensure_writable (notmuch);
+ if (ret)
+ return ret;
+
+ path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH);
+ if (! path)
+ return NOTMUCH_STATUS_PATH_ERROR;
+
+ local = talloc_new (NULL);
+ if (! local)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ ret = _notmuch_choose_xapian_path (local, path, &xapian_path, &message);
+ if (ret)
goto DONE;
- }
if (! (compact_xapian_path = talloc_asprintf (local, "%s.compact", xapian_path))) {
ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
try {
NotmuchCompactor compactor (status_cb, closure);
- notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0, compactor);
+ notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0,
+ compactor);
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg ().c_str ());
ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
const char *
notmuch_database_get_path (notmuch_database_t *notmuch)
{
- return notmuch->path;
+ return notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH);
}
unsigned int
*/
notmuch_status_t
notmuch_database_upgrade (notmuch_database_t *notmuch,
- void (*progress_notify) (void *closure,
- double progress),
+ void (*progress_notify)(void *closure,
+ double progress),
void *closure)
{
void *local = talloc_new (NULL);
if (private_status) {
_notmuch_database_log (notmuch,
"Upgrade failed while creating ghost messages.\n");
- status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
+ status = COERCE_STATUS (private_status,
+ "Unexpected status from _notmuch_message_initialize_ghost");
goto DONE;
}
}
status = NOTMUCH_STATUS_SUCCESS;
- db->set_metadata ("features", _print_features (local, notmuch->features));
+ db->set_metadata ("features", _notmuch_database_print_features (local, notmuch->features));
db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
DONE:
const char *db_path, *relative;
unsigned int db_path_len;
- db_path = notmuch_database_get_path (notmuch);
+ db_path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
db_path_len = strlen (db_path);
relative = path;
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
}
} catch (const Xapian::Error &error) {
- _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
+ _notmuch_database_log (notmuch,
+ "Error: A Xapian exception occurred finding message by filename: %s\n",
error.get_msg ().c_str ());
notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
#define LOG_XAPIAN_EXCEPTION(directory, error) _log_xapian_exception (__location__, directory, error)
static void
-_log_xapian_exception (const char *where, notmuch_directory_t *dir, const Xapian::Error error) {
+_log_xapian_exception (const char *where, notmuch_directory_t *dir, const Xapian::Error error)
+{
notmuch_database_t *notmuch = dir->notmuch;
+
_notmuch_database_log (notmuch,
"A Xapian exception occurred at %s: %s\n",
where,
--- /dev/null
+#include "database-private.h"
+
+static const struct {
+ /* NOTMUCH_FEATURE_* value. */
+ _notmuch_features value;
+ /* Feature name as it appears in the database. This name should
+ * be appropriate for displaying to the user if an older version
+ * of notmuch doesn't support this feature. */
+ const char *name;
+ /* Compatibility flags when this feature is declared. */
+ const char *flags;
+} feature_names[] = {
+ { NOTMUCH_FEATURE_FILE_TERMS,
+ "multiple paths per message", "rw" },
+ { NOTMUCH_FEATURE_DIRECTORY_DOCS,
+ "relative directory paths", "rw" },
+ /* Header values are not required for reading a database because a
+ * reader can just refer to the message file. */
+ { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
+ "from/subject/message-ID in database", "w" },
+ { NOTMUCH_FEATURE_BOOL_FOLDER,
+ "exact folder:/path: search", "rw" },
+ { NOTMUCH_FEATURE_GHOSTS,
+ "mail documents for missing messages", "w" },
+ /* Knowledge of the index mime-types are not required for reading
+ * a database because a reader will just be unable to query
+ * them. */
+ { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
+ "indexed MIME types", "w" },
+ { NOTMUCH_FEATURE_LAST_MOD,
+ "modification tracking", "w" },
+ /* Existing databases will work fine for all queries not involving
+ * 'body:' */
+ { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
+ "index body and headers separately", "w" },
+};
+
+char *
+_notmuch_database_print_features (const void *ctx, unsigned int features)
+{
+ unsigned int i;
+ char *res = talloc_strdup (ctx, "");
+
+ for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
+ if (features & feature_names[i].value)
+ res = talloc_asprintf_append_buffer (
+ res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
+
+ return res;
+}
+
+
+/* Parse a database features string from the given database version.
+ * Returns the feature bit set.
+ *
+ * For version < 3, this ignores the features string and returns a
+ * hard-coded set of features.
+ *
+ * If there are unrecognized features that are required to open the
+ * database in mode (which should be 'r' or 'w'), return a
+ * comma-separated list of unrecognized but required features in
+ * *incompat_out suitable for presenting to the user. *incompat_out
+ * will be allocated from ctx.
+ */
+_notmuch_features
+_notmuch_database_parse_features (const void *ctx, const char *features, unsigned int version,
+ char mode, char **incompat_out)
+{
+ _notmuch_features res = static_cast<_notmuch_features>(0);
+ unsigned int namelen, i;
+ size_t llen = 0;
+ const char *flags;
+
+ /* Prior to database version 3, features were implied by the
+ * version number. */
+ if (version == 0)
+ return NOTMUCH_FEATURES_V0;
+ else if (version == 1)
+ return NOTMUCH_FEATURES_V1;
+ else if (version == 2)
+ return NOTMUCH_FEATURES_V2;
+
+ /* Parse the features string */
+ while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
+ flags = strchr (features, '\t');
+ if (! flags || flags > features + llen)
+ continue;
+ namelen = flags - features;
+
+ for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
+ if (strlen (feature_names[i].name) == namelen &&
+ strncmp (feature_names[i].name, features, namelen) == 0) {
+ res |= feature_names[i].value;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE (feature_names) && incompat_out) {
+ /* Unrecognized feature */
+ const char *have = strchr (flags, mode);
+ if (have && have < features + llen) {
+ /* This feature is required to access this database in
+ * 'mode', but we don't understand it. */
+ if (! *incompat_out)
+ *incompat_out = talloc_strdup (ctx, "");
+ *incompat_out = talloc_asprintf_append_buffer (
+ *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
+ namelen, features);
+ }
+ }
+ }
+
+ return res;
+}
.value_table = NULL,
};
- type = g_type_register_static (GMIME_TYPE_FILTER, "NotmuchFilterDiscardNonTerm", &info, (GTypeFlags) 0);
+ type = g_type_register_static (GMIME_TYPE_FILTER, "NotmuchFilterDiscardNonTerm", &info,
+ (GTypeFlags) 0);
}
filter = (NotmuchFilterDiscardNonTerm *) g_object_new (type, NULL);
msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
toindex = _notmuch_repair_crypto_payload_skip_legacy_display (child);
if (toindex != child)
- notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display");
+ notmuch_message_add_property (message, "index.repaired",
+ "skip-protected-headers-legacy-display");
}
_index_mime_part (message, indexopts, toindex, msg_crypto);
}
mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
- _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message), msg_crypto);
+ _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message),
+ msg_crypto);
goto DONE;
}
bool attempted = false;
GMimeDecryptResult *decrypt_result = NULL;
bool get_sk = (notmuch_indexopts_get_decrypt_policy (indexopts) == NOTMUCH_DECRYPT_TRUE);
+
clear = _notmuch_crypto_decrypt (&attempted, notmuch_indexopts_get_decrypt_policy (indexopts),
message, encrypted_data, get_sk ? &decrypt_result : NULL, &err);
if (! attempted)
notmuch_status_to_string (status));
if (get_sk) {
status = notmuch_message_add_property (message, "session-key",
- g_mime_decrypt_result_get_session_key (decrypt_result));
+ g_mime_decrypt_result_get_session_key (
+ decrypt_result));
if (status)
_notmuch_database_log (notmuch, "failed to add session-key "
"property (%d)\n", status);
g_object_unref (decrypt_result);
}
GMimeObject *toindex = clear;
- if (_notmuch_message_crypto_potential_payload (msg_crypto, clear, encrypted_data, GMIME_MULTIPART_ENCRYPTED_CONTENT) &&
+
+ if (_notmuch_message_crypto_potential_payload (msg_crypto, clear, encrypted_data,
+ GMIME_MULTIPART_ENCRYPTED_CONTENT) &&
msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
toindex = _notmuch_repair_crypto_payload_skip_legacy_display (clear);
if (toindex != clear)
- notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display");
+ notmuch_message_add_property (message, "index.repaired",
+ "skip-protected-headers-legacy-display");
}
_index_mime_part (message, indexopts, toindex, msg_crypto);
g_object_unref (clear);
if (p7type == GMIME_SECURE_MIME_TYPE_SIGNED_DATA) {
sigs = g_mime_application_pkcs7_mime_verify (pkcs7, GMIME_VERIFY_NONE, &mimeobj, &err);
if (sigs == NULL) {
- _notmuch_database_log (notmuch, "Failed to verify PKCS#7 SignedData during indexing. (%d:%d) [%s]\n",
+ _notmuch_database_log (notmuch,
+ "Failed to verify PKCS#7 SignedData during indexing. (%d:%d) [%s]\n",
err->domain, err->code, err->message);
g_error_free (err);
goto DONE;
msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
toindex = _notmuch_repair_crypto_payload_skip_legacy_display (mimeobj);
if (toindex != mimeobj)
- notmuch_message_add_property (message, "index.repaired", "skip-protected-headers-legacy-display");
+ notmuch_message_add_property (message, "index.repaired",
+ "skip-protected-headers-legacy-display");
}
_index_mime_part (message, indexopts, toindex, msg_crypto);
} else if (p7type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) {
_notmuch_database_log (notmuch, "Cannot currently handle PKCS#7 smime-type '%s'\n",
g_mime_object_get_content_type_parameter (part, "smime-type"));
}
- DONE:
+ DONE:
if (mimeobj)
g_object_unref (mimeobj);
if (sigs)
char *decrypt_policy;
notmuch_status_t err = notmuch_database_get_config (db, "index.decrypt", &decrypt_policy);
+
if (err)
return NULL;
if (unlikely (message == NULL))
return NULL;
- const char *prefix = notmuch_database_get_path (notmuch);
+ const char *prefix = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
+
if (prefix == NULL)
goto FAIL;
if (*filename == '/') {
- if (strncmp (filename, prefix, strlen(prefix)) != 0) {
+ if (strncmp (filename, prefix, strlen (prefix)) != 0) {
_notmuch_database_log (notmuch, "Error opening %s: path outside mail root\n",
filename);
errno = 0;
}
message->filename = talloc_strdup (message, filename);
} else {
- message->filename = talloc_asprintf(message, "%s/%s", prefix, filename);
+ message->filename = talloc_asprintf (message, "%s/%s", prefix, filename);
}
if (message->filename == NULL)
return NOTMUCH_STATUS_NULL_POINTER;
notmuch_string_map_t *map;
+
map = _notmuch_message_property_map (message);
if (! map)
return NOTMUCH_STATUS_NULL_POINTER;
notmuch_string_map_iterator_t *matcher = _notmuch_string_map_iterator_create (map, key, true);
+
if (! matcher)
return NOTMUCH_STATUS_OUT_OF_MEMORY;
#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
static void
-_log_xapian_exception (const char *where, notmuch_message_t *message, const Xapian::Error error) {
+_log_xapian_exception (const char *where, notmuch_message_t *message, const Xapian::Error error)
+{
notmuch_database_t *notmuch = notmuch_message_get_database (message);
+
_notmuch_database_log (notmuch,
"A Xapian exception occurred at %s: %s\n",
where,
doc_id = _notmuch_database_generate_doc_id (notmuch);
} catch (const Xapian::Error &error) {
- _notmuch_database_log (notmuch_message_get_database (message), "A Xapian exception occurred creating message: %s\n",
+ _notmuch_database_log (notmuch_message_get_database (message),
+ "A Xapian exception occurred creating message: %s\n",
error.get_msg ().c_str ());
notmuch->exception_reported = true;
*status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
return NULL;
const std::string &term = *i;
+
if (strncmp (term.c_str (), prefix, prefix_len))
return NULL;
/* all the way without an exception */
break;
} catch (const Xapian::DatabaseModifiedError &error) {
- notmuch_status_t status = _notmuch_database_reopen (message->notmuch);
+ notmuch_status_t status = notmuch_database_reopen (message->notmuch,
+ NOTMUCH_DATABASE_MODE_READ_ONLY);
if (status != NOTMUCH_STATUS_SUCCESS)
INTERNAL_ERROR ("unhandled error from notmuch_database_reopen: %s\n",
notmuch_status_to_string (status));
*colon = '\0';
- db_path = notmuch_database_get_path (message->notmuch);
+ db_path = notmuch_config_get (message->notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
directory = _notmuch_database_get_directory_path (local,
message->notmuch,
_notmuch_message_delete (notmuch_message_t *message)
{
notmuch_status_t status;
- const char *mid, *tid, *query_string;
+ const char *mid, *tid;
notmuch_message_t *ghost;
notmuch_private_status_t private_status;
notmuch_database_t *notmuch;
- notmuch_query_t *query;
unsigned int count = 0;
bool is_ghost;
if (is_ghost)
return NOTMUCH_STATUS_SUCCESS;
- query_string = talloc_asprintf (message, "thread:%s", tid);
- query = notmuch_query_create (notmuch, query_string);
- if (query == NULL)
- return NOTMUCH_STATUS_OUT_OF_MEMORY;
- status = notmuch_query_count_messages (query, &count);
- if (status) {
- notmuch_query_destroy (query);
- return status;
+ /* look for a non-ghost message in the same thread */
+ try {
+ Xapian::PostingIterator thread_doc, thread_doc_end;
+ Xapian::PostingIterator mail_doc, mail_doc_end;
+
+ _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &thread_doc,
+ &thread_doc_end);
+ _notmuch_database_find_doc_ids (message->notmuch, "type", "mail", &mail_doc, &mail_doc_end);
+
+ while (count == 0 &&
+ thread_doc != thread_doc_end &&
+ mail_doc != mail_doc_end) {
+ thread_doc.skip_to (*mail_doc);
+ if (thread_doc != thread_doc_end) {
+ if (*thread_doc == *mail_doc) {
+ count++;
+ } else {
+ mail_doc.skip_to (*thread_doc);
+ if (mail_doc != mail_doc_end && *thread_doc == *mail_doc)
+ count++;
+ }
+ }
+ }
+ } catch (Xapian::Error &error) {
+ LOG_XAPIAN_EXCEPTION (message, error);
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
-
if (count > 0) {
/* reintroduce a ghost in its place because there are still
* other active messages in this thread: */
notmuch_message_destroy (ghost);
status = COERCE_STATUS (private_status, "Error converting to ghost message");
} else {
- /* the thread is empty; drop all ghost messages from it */
- notmuch_messages_t *messages;
- status = _notmuch_query_search_documents (query,
- "ghost",
- &messages);
- if (status == NOTMUCH_STATUS_SUCCESS) {
- notmuch_status_t last_error = NOTMUCH_STATUS_SUCCESS;
- while (notmuch_messages_valid (messages)) {
- message = notmuch_messages_get (messages);
- status = _notmuch_message_delete (message);
- if (status) /* we'll report the last failure we see;
- * if there is more than one failure, we
- * forget about previous ones */
- last_error = status;
- notmuch_message_destroy (message);
- notmuch_messages_move_to_next (messages);
+ /* the thread now contains only ghosts: delete them */
+ try {
+ Xapian::PostingIterator doc, doc_end;
+
+ _notmuch_database_find_doc_ids (message->notmuch, "thread", tid, &doc, &doc_end);
+
+ for (; doc != doc_end; doc++) {
+ message->notmuch->writable_xapian_db->delete_document (*doc);
}
- status = last_error;
+ } catch (Xapian::Error &error) {
+ LOG_XAPIAN_EXCEPTION (message, error);
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
+
}
- notmuch_query_destroy (query);
return status;
}
{
notmuch_status_t status;
notmuch_bool_t ret;
+
status = notmuch_message_has_maildir_flag_st (message, flag, &ret);
if (status)
return FALSE;
notmuch_bool_t *is_set)
{
notmuch_status_t status;
-
+
if (! is_set)
return NOTMUCH_STATUS_NULL_POINTER;
status = _ensure_maildir_flags (message, false);
if (status)
return status;
-
+
*is_set = message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL);
return NOTMUCH_STATUS_SUCCESS;
}
private_status = _notmuch_message_remove_term (message, "tag", tag);
if (private_status) {
return COERCE_STATUS (private_status,
- "_notmuch_message_remove_term return unexpected value: %d\n",
- private_status);
+ "_notmuch_message_remove_term return unexpected value: %d\n",
+ private_status);
}
}
orig_thread_id = notmuch_message_get_thread_id (message);
if (! orig_thread_id) {
/* the following is correct as long as there is only one reason
- n_m_get_thread_id returns NULL
- */
+ * n_m_get_thread_id returns NULL
+ */
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
#define NOTMUCH_CLEAR_BIT(valp, bit) \
(_NOTMUCH_VALID_BIT (bit) ? (*(valp) &= ~(1ull << (bit))) : *(valp))
-#define unused(x) x __attribute__ ((unused))
+#define unused(x) x ## _unused __attribute__ ((unused))
/* Thanks to Andrew Tridgell's (SAMBA's) talloc for this definition of
* unlikely. The talloc source code comes to us via the GNU LGPL v. 3.
NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY = NOTMUCH_STATUS_OUT_OF_MEMORY,
NOTMUCH_PRIVATE_STATUS_READ_ONLY_DATABASE = NOTMUCH_STATUS_READ_ONLY_DATABASE,
NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION = NOTMUCH_STATUS_XAPIAN_EXCEPTION,
+ NOTMUCH_PRIVATE_STATUS_FILE_ERROR = NOTMUCH_STATUS_FILE_ERROR,
NOTMUCH_PRIVATE_STATUS_FILE_NOT_EMAIL = NOTMUCH_STATUS_FILE_NOT_EMAIL,
NOTMUCH_PRIVATE_STATUS_NULL_POINTER = NOTMUCH_STATUS_NULL_POINTER,
NOTMUCH_PRIVATE_STATUS_TAG_TOO_LONG = NOTMUCH_STATUS_TAG_TOO_LONG,
NOTMUCH_PRIVATE_STATUS_UNBALANCED_FREEZE_THAW = NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW,
+ NOTMUCH_PRIVATE_STATUS_UNBALANCED_ATOMIC = NOTMUCH_STATUS_UNBALANCED_ATOMIC,
+ NOTMUCH_PRIVATE_STATUS_UNSUPPORTED_OPERATION = NOTMUCH_STATUS_UNSUPPORTED_OPERATION,
+ NOTMUCH_PRIVATE_STATUS_UPGRADE_REQUIRED = NOTMUCH_STATUS_UPGRADE_REQUIRED,
+ NOTMUCH_PRIVATE_STATUS_PATH_ERROR = NOTMUCH_STATUS_PATH_ERROR,
+ NOTMUCH_PRIVATE_STATUS_IGNORED = NOTMUCH_STATUS_IGNORED,
+ NOTMUCH_PRIVATE_STATUS_ILLEGAL_ARGUMENT = NOTMUCH_STATUS_ILLEGAL_ARGUMENT,
+ NOTMUCH_PRIVATE_STATUS_MALFORMED_CRYPTO_PROTOCOL = NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL,
+ NOTMUCH_PRIVATE_STATUS_FAILED_CRYPTO_CONTEXT_CREATION = NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION,
+ NOTMUCH_PRIVATE_STATUS_UNKNOWN_CRYPTO_PROTOCOL = NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL,
+ NOTMUCH_PRIVATE_STATUS_NO_CONFIG = NOTMUCH_STATUS_NO_CONFIG,
+ NOTMUCH_PRIVATE_STATUS_NO_DATABASE = NOTMUCH_STATUS_NO_DATABASE,
+ NOTMUCH_PRIVATE_STATUS_DATABASE_EXISTS = NOTMUCH_STATUS_DATABASE_EXISTS,
/* Then add our own private values. */
NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG = NOTMUCH_STATUS_LAST_STATUS,
notmuch_status_t
_notmuch_database_ensure_writable (notmuch_database_t *notmuch);
-notmuch_status_t
-_notmuch_database_reopen (notmuch_database_t *notmuch);
-
void
_notmuch_database_log (notmuch_database_t *notmuch,
const char *format, ...);
const char *key,
const char *value);
+void
+_notmuch_string_map_set (notmuch_string_map_t *map,
+ const char *key,
+ const char *value);
+
const char *
_notmuch_string_map_get (notmuch_string_map_t *map, const char *key);
#define EMPTY_STRING(s) ((s)[0] == '\0')
+/* config.cc */
+notmuch_status_t
+_notmuch_config_load_from_database (notmuch_database_t *db);
+
+notmuch_status_t
+_notmuch_config_load_from_file (notmuch_database_t *db, GKeyFile *file);
+
+notmuch_status_t
+_notmuch_config_load_defaults (notmuch_database_t *db);
+
+void
+_notmuch_config_cache (notmuch_database_t *db, notmuch_config_key_t key, const char *val);
+
+/* open.cc */
+notmuch_status_t
+_notmuch_choose_xapian_path (void *ctx, const char *database_path, const char **xapian_path,
+ char **message);
+
NOTMUCH_END_DECLS
#ifdef __cplusplus
* version in Makefile.local.
*/
#define LIBNOTMUCH_MAJOR_VERSION 5
-#define LIBNOTMUCH_MINOR_VERSION 3
+#define LIBNOTMUCH_MINOR_VERSION 4
#define LIBNOTMUCH_MICRO_VERSION 0
* something that notmuch doesn't know how to handle.
*/
NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL,
+ /**
+ * Unable to load a config file
+ */
+ NOTMUCH_STATUS_NO_CONFIG,
+ /**
+ * Unable to load a database
+ */
+ NOTMUCH_STATUS_NO_DATABASE,
+ /**
+ * Database exists, so not (re)-created
+ */
+ NOTMUCH_STATUS_DATABASE_EXISTS,
/**
* Not an actual status value. Just a way to find out how many
* valid status values there are.
typedef struct _notmuch_directory notmuch_directory_t;
typedef struct _notmuch_filenames notmuch_filenames_t;
typedef struct _notmuch_config_list notmuch_config_list_t;
+typedef struct _notmuch_config_values notmuch_config_values_t;
+typedef struct _notmuch_config_pairs notmuch_config_pairs_t;
typedef struct _notmuch_indexopts notmuch_indexopts_t;
#endif /* __DOXYGEN__ */
} notmuch_database_mode_t;
/**
- * Open an existing notmuch database located at 'path'.
+ * Deprecated alias for notmuch_database_open_with_config with
+ * config_path="" and error_message=NULL
+ * @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32)
+ */
+/* NOTMUCH_DEPRECATED(5, 4) */
+notmuch_status_t
+notmuch_database_open (const char *path,
+ notmuch_database_mode_t mode,
+ notmuch_database_t **database);
+/**
+ * Deprecated alias for notmuch_database_open_with_config with
+ * config_path=""
+ *
+ * @deprecated Deprecated as of libnotmuch 5.4 (notmuch 0.32)
+ *
+ */
+/* NOTMUCH_DEPRECATED(5, 4) */
+notmuch_status_t
+notmuch_database_open_verbose (const char *path,
+ notmuch_database_mode_t mode,
+ notmuch_database_t **database,
+ char **error_message);
+
+/**
+ * Open an existing notmuch database located at 'database_path', using
+ * configuration in 'config_path'.
+ *
+ * @param[in] database_path
+ * @parblock
+ * Path to existing database.
+ *
+ * A notmuch database is a Xapian database containing appropriate
+ * metadata.
*
* The database should have been created at some time in the past,
* (not necessarily by this process), by calling
- * notmuch_database_create with 'path'. By default the database should be
- * opened for reading only. In order to write to the database you need to
- * pass the NOTMUCH_DATABASE_MODE_READ_WRITE mode.
+ * notmuch_database_create.
+ *
+ * If 'database_path' is NULL, use the location specified
+ *
+ * - in the environment variable NOTMUCH_DATABASE, if non-empty
+ *
+ * - in a configuration file, located as described under 'config_path'
+ *
+ * - by $XDG_DATA_HOME/notmuch/$PROFILE where XDG_DATA_HOME defaults
+ * to "$HOME/.local/share" and PROFILE as as discussed in
+ * 'profile'
*
- * An existing notmuch database can be identified by the presence of a
- * directory named ".notmuch" below 'path'.
+ * If 'database_path' is non-NULL, but does not appear to be a Xapian
+ * database, check for a directory '.notmuch/xapian' below
+ * 'database_path' (this is the behavior of
+ * notmuch_database_open_verbose pre-0.32).
+ *
+ * @endparblock
+ * @param[in] mode
+ * @parblock
+ * Mode to open database. Use one of #NOTMUCH_DATABASE_MODE_READ_WRITE
+ * or #NOTMUCH_DATABASE_MODE_READ_ONLY
+ *
+ * @endparblock
+ * @param[in] config_path
+ * @parblock
+ * Path to config file.
+ *
+ * Config file is key-value, with mandatory sections. See
+ * <em>notmuch-config(5)</em> for more information. The key-value pair
+ * overrides the corresponding configuration data stored in the
+ * database (see <em>notmuch_database_get_config</em>)
+ *
+ * If <em>config_path</em> is NULL use the path specified
+ *
+ * - in environment variable <em>NOTMUCH_CONFIG</em>, if non-empty
+ *
+ * - by <em>XDG_CONFIG_HOME</em>/notmuch/ where
+ * XDG_CONFIG_HOME defaults to "$HOME/.config".
+ *
+ * - by $HOME/.notmuch-config
+ *
+ * If <em>config_path</em> is "" (empty string) then do not
+ * open any configuration file.
+ * @endparblock
+ * @param[in] profile:
+ * @parblock
+ * Name of profile (configuration/database variant).
+ *
+ * If non-NULL, append to the directory / file path determined for
+ * <em>config_path</em> and <em>database_path</em>.
+ *
+ * If NULL then use
+ * - environment variable NOTMUCH_PROFILE if defined,
+ * - otherwise "default" for directories and "" (empty string) for paths.
+ *
+ * @endparblock
+ * @param[out] database
+ * @parblock
+ * Pointer to database object. May not be NULL.
*
* The caller should call notmuch_database_destroy when finished with
* this database.
*
* In case of any failure, this function returns an error status and
- * sets *database to NULL (after printing an error message on stderr).
+ * sets *database to NULL.
*
- * Return value:
+ * @endparblock
+ * @param[out] error_message
+ * If non-NULL, store error message from opening the database.
+ * Any such message is allocated by \a malloc(3) and should be freed
+ * by the caller.
*
- * NOTMUCH_STATUS_SUCCESS: Successfully opened the database.
+ * @retval NOTMUCH_STATUS_SUCCESS: Successfully opened the database.
*
- * NOTMUCH_STATUS_NULL_POINTER: The given 'path' argument is NULL.
+ * @retval NOTMUCH_STATUS_NULL_POINTER: The given \a database
+ * argument is NULL.
*
- * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
+ * @retval NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
*
- * NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the
- * database file (such as permission denied, or file not found,
+ * @retval NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the
+ * database or config file (such as permission denied, or file not found,
* etc.), or the database version is unknown.
*
- * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred.
+ * @retval NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred.
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
*/
+
notmuch_status_t
-notmuch_database_open (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database);
+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);
+
+
/**
- * Like notmuch_database_open, except optionally return an error
- * message. This message is allocated by malloc and should be freed by
- * the caller.
+ * Loads configuration from config file, database, and/or defaults
+ *
+ * For description of arguments, @see notmuch_database_open_with_config
+ *
+ * @retval NOTMUCH_STATUS_SUCCESS: Successfully loaded configuration.
+ *
+ * @retval NOTMUCH_STATUS_NO_CONFIG: No config file was loaded. Not fatal.
+ *
+ * @retval NOTMUCH_STATUS_NO_DATABASE: No config information was
+ * loaded from a database. Not fatal.
+ *
+ * @retval NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
+ *
+ * @retval NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the
+ * database or config file (such as permission denied, or file not found,
+ * etc.)
+ *
+ * @retval NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred.
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
*/
notmuch_status_t
-notmuch_database_open_verbose (const char *path,
- notmuch_database_mode_t mode,
- notmuch_database_t **database,
- char **error_message);
+notmuch_database_load_config (const char *database_path,
+ const char *config_path,
+ const char *profile,
+ notmuch_database_t **database,
+ char **error_message);
+
+/**
+ * Create a new notmuch database located at 'database_path', using
+ * configuration in 'config_path'.
+ *
+ * For description of arguments, @see notmuch_database_open_with_config
+ *
+ * @retval NOTMUCH_STATUS_SUCCESS: Successfully created the database.
+ *
+ * @retval NOTMUCH_STATUS_DATABASE_EXISTS: Database already exists, not created
+ *
+ * @retval NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.
+ *
+ * @retval NOTMUCH_STATUS_FILE_ERROR: An error occurred trying to open the
+ * database or config file (such as permission denied, or file not found,
+ * etc.)
+ *
+ * @retval NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred.
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ */
+
+notmuch_status_t
+notmuch_database_create_with_config (const char *database_path,
+ const char *config_path,
+ const char *profile,
+ notmuch_database_t **database,
+ char **error_message);
/**
* Retrieve last status string for given database.
notmuch_compact_status_cb_t status_cb,
void *closure);
+/**
+ * Like notmuch_database_compact, but take an open database as a
+ * parameter.
+ *
+ * @since libnnotmuch 5.4 (notmuch 0.32)
+ */
+notmuch_status_t
+notmuch_database_compact_db (notmuch_database_t *database,
+ const char *backup_path,
+ notmuch_compact_status_cb_t status_cb,
+ void *closure);
+
/**
* Destroy the notmuch database, closing it if necessary and freeing
* all associated resources.
notmuch_tags_t *
notmuch_database_get_all_tags (notmuch_database_t *db);
+/**
+ * Reopen an open notmuch database.
+ *
+ * @param [in] db open notmuch database
+ * @param [in] mode mode (read only or read-write) for reopened database.
+ *
+ * @retval #NOTMUCH_STATUS_SUCCESS
+ * @retval #NOTMUCH_STATUS_ILLEGAL_ARGUMENT The passed database was not open.
+ * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION A Xapian exception occured
+ */
+notmuch_status_t
+notmuch_database_reopen (notmuch_database_t *db, notmuch_database_mode_t mode);
+
/**
* Create a new query for 'database'.
*
* @deprecated Deprecated as of libnotmuch 5.3 (notmuch 0.31). Please
* use notmuch_message_get_flag_st instead.
*/
-NOTMUCH_DEPRECATED(5,3)
+NOTMUCH_DEPRECATED (5, 3)
notmuch_bool_t
notmuch_message_get_flag (notmuch_message_t *message,
notmuch_message_flag_t flag);
* @returns FALSE in case of error
* @deprecated libnotmuch 5.3 (notmuch 0.31)
*/
-NOTMUCH_DEPRECATED(5, 3)
+NOTMUCH_DEPRECATED (5, 3)
notmuch_bool_t
notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag);
* set config 'key' to 'value'
*
* @since libnotmuch 4.4 (notmuch 0.23)
+ * @retval #NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in
+ * read-only mode so message cannot be modified.
+ * @retval #NOTMUCH_STATUS_XAPIAN_EXCEPTION: an exception was thrown
+ * accessing the database.
+ * @retval #NOTMUCH_STATUS_SUCCESS
*/
notmuch_status_t
notmuch_database_set_config (notmuch_database_t *db, const char *key, const char *value);
* caller.
*
* @since libnotmuch 4.4 (notmuch 0.23)
+ *
*/
notmuch_status_t
notmuch_database_get_config (notmuch_database_t *db, const char *key, char **value);
* @since libnotmuch 4.4 (notmuch 0.23)
*/
notmuch_status_t
-notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix, notmuch_config_list_t **out);
+notmuch_database_get_config_list (notmuch_database_t *db, const char *prefix,
+ notmuch_config_list_t **out);
/**
* Is 'config_list' iterator valid (i.e. _key, _value, _move_to_next can be called).
void
notmuch_config_list_destroy (notmuch_config_list_t *config_list);
+/**
+ * Configuration keys known to libnotmuch
+ */
+typedef enum _notmuch_config_key {
+ NOTMUCH_CONFIG_FIRST,
+ NOTMUCH_CONFIG_DATABASE_PATH = NOTMUCH_CONFIG_FIRST,
+ NOTMUCH_CONFIG_MAIL_ROOT,
+ NOTMUCH_CONFIG_HOOK_DIR,
+ NOTMUCH_CONFIG_BACKUP_DIR,
+ NOTMUCH_CONFIG_EXCLUDE_TAGS,
+ NOTMUCH_CONFIG_NEW_TAGS,
+ NOTMUCH_CONFIG_NEW_IGNORE,
+ NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS,
+ NOTMUCH_CONFIG_PRIMARY_EMAIL,
+ NOTMUCH_CONFIG_OTHER_EMAIL,
+ NOTMUCH_CONFIG_USER_NAME,
+ NOTMUCH_CONFIG_LAST
+} notmuch_config_key_t;
+
+/**
+ * get a configuration value from an open database.
+ *
+ * This value reflects all configuration information given at the time
+ * the database was opened.
+ *
+ * @param[in] notmuch database
+ * @param[in] key configuration key
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval NULL if 'key' unknown or if no value is known for
+ * 'key'. Otherwise returns a string owned by notmuch which should
+ * not be modified nor freed by the caller.
+ */
+const char *
+notmuch_config_get (notmuch_database_t *notmuch, notmuch_config_key_t key);
+
+/**
+ * set a configuration value from in an open database.
+ *
+ * This value reflects all configuration information given at the time
+ * the database was opened.
+ *
+ * @param[in,out] notmuch database open read/write
+ * @param[in] key configuration key
+ * @param[in] val configuration value
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval returns any return value for notmuch_database_set_config.
+ */
+notmuch_status_t
+notmuch_config_set (notmuch_database_t *notmuch, notmuch_config_key_t key, const char *val);
+
+/**
+ * Returns an iterator for a ';'-delimited list of configuration values
+ *
+ * These values reflect all configuration information given at the
+ * time the database was opened.
+ *
+ * @param[in] notmuch database
+ * @param[in] key configuration key
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval NULL in case of error.
+ */
+notmuch_config_values_t *
+notmuch_config_get_values (notmuch_database_t *notmuch, notmuch_config_key_t key);
+
+/**
+ * Returns an iterator for a ';'-delimited list of configuration values
+ *
+ * These values reflect all configuration information given at the
+ * time the database was opened.
+ *
+ * @param[in] notmuch database
+ * @param[in] key configuration key
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval NULL in case of error.
+ */
+notmuch_config_values_t *
+notmuch_config_get_values_string (notmuch_database_t *notmuch, const char *key);
+
+/**
+ * Is the given 'config_values' iterator pointing at a valid element.
+ *
+ * @param[in] values iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval FALSE if passed a NULL pointer, or the iterator is exhausted.
+ *
+ */
+notmuch_bool_t
+notmuch_config_values_valid (notmuch_config_values_t *values);
+
+/**
+ * Get the current value from the 'values' iterator
+ *
+ * @param[in] values iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval a string with the same lifetime as the iterator
+ */
+const char *
+notmuch_config_values_get (notmuch_config_values_t *values);
+
+/**
+ * Move the 'values' iterator to the next element
+ *
+ * @param[in,out] values iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ */
+void
+notmuch_config_values_move_to_next (notmuch_config_values_t *values);
+
+
+/**
+ * reset the 'values' iterator to the first element
+ *
+ * @param[in,out] values iterator. A NULL value is ignored.
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ */
+void
+notmuch_config_values_start (notmuch_config_values_t *values);
+
+/**
+ * Destroy a config values iterator, along with any associated
+ * resources.
+ *
+ * @param[in,out] values iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ */
+void
+notmuch_config_values_destroy (notmuch_config_values_t *values);
+
+
+/**
+ * Returns an iterator for a (key, value) configuration pairs
+ *
+ * @param[in] notmuch database
+ * @param[in] prefix prefix for keys. Pass "" for all keys.
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval NULL in case of error.
+ */
+notmuch_config_pairs_t *
+notmuch_config_get_pairs (notmuch_database_t *notmuch,
+ const char *prefix);
+
+/**
+ * Is the given 'config_pairs' iterator pointing at a valid element.
+ *
+ * @param[in] pairs iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval FALSE if passed a NULL pointer, or the iterator is exhausted.
+ *
+ */
+notmuch_bool_t
+notmuch_config_pairs_valid (notmuch_config_pairs_t *pairs);
+
+/**
+ * Move the 'config_pairs' iterator to the next element
+ *
+ * @param[in,out] pairs iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ */
+void
+notmuch_config_pairs_move_to_next (notmuch_config_pairs_t *pairs);
+
+/**
+ * Get the current key from the 'config_pairs' iterator
+ *
+ * @param[in] pairs iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval a string with the same lifetime as the iterator
+ */
+const char *
+notmuch_config_pairs_key (notmuch_config_pairs_t *pairs);
+
+/**
+ * Get the current value from the 'config_pairs' iterator
+ *
+ * @param[in] pairs iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval a string with the same lifetime as the iterator
+ */
+const char *
+notmuch_config_pairs_value (notmuch_config_pairs_t *pairs);
+
+/**
+ * Destroy a config_pairs iterator, along with any associated
+ * resources.
+ *
+ * @param[in,out] pairs iterator
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ */
+void
+notmuch_config_pairs_destroy (notmuch_config_pairs_t *pairs);
+
+/**
+ * get a configuration value from an open database as Boolean
+ *
+ * This value reflects all configuration information given at the time
+ * the database was opened.
+ *
+ * @param[in] notmuch database
+ * @param[in] key configuration key
+ * @param[out] val configuration value, converted to Boolean
+ *
+ * @since libnotmuch 5.4 (notmuch 0.32)
+ *
+ * @retval #NOTMUCH_STATUS_ILLEGAL_ARGUMENT if either key is unknown
+ * or the corresponding value does not convert to Boolean.
+ */
+notmuch_status_t
+notmuch_config_get_bool (notmuch_database_t *notmuch,
+ notmuch_config_key_t key,
+ notmuch_bool_t *val);
+
+/**
+ * return the path of the config file loaded, if any
+ *
+ * @retval NULL if no config file was loaded
+ */
+const char *
+notmuch_config_path (notmuch_database_t *notmuch);
/**
* get the current default indexing options for a given database.
--- /dev/null
+#include <unistd.h>
+#include <libgen.h>
+
+#include "database-private.h"
+#include "parse-time-vrp.h"
+#include "path-util.h"
+
+#if HAVE_XAPIAN_DB_RETRY_LOCK
+#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
+#else
+#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
+#endif
+
+notmuch_status_t
+notmuch_database_open (const char *path,
+ notmuch_database_mode_t mode,
+ notmuch_database_t **database)
+{
+ char *status_string = NULL;
+ notmuch_status_t status;
+
+ status = notmuch_database_open_verbose (path, mode, database,
+ &status_string);
+
+ if (status_string) {
+ fputs (status_string, stderr);
+ free (status_string);
+ }
+
+ return status;
+}
+
+notmuch_status_t
+notmuch_database_open_verbose (const char *path,
+ notmuch_database_mode_t mode,
+ notmuch_database_t **database,
+ char **status_string)
+{
+ return notmuch_database_open_with_config (path, mode, "", NULL,
+ database, status_string);
+}
+
+static const char *
+_xdg_dir (void *ctx,
+ const char *xdg_root_variable,
+ const char *xdg_prefix,
+ const char *profile_name)
+{
+ const char *xdg_root = getenv (xdg_root_variable);
+
+ if (! xdg_root) {
+ const char *home = getenv ("HOME");
+
+ if (! home) return NULL;
+
+ xdg_root = talloc_asprintf (ctx,
+ "%s/%s",
+ home,
+ xdg_prefix);
+ }
+
+ if (! profile_name)
+ profile_name = getenv ("NOTMUCH_PROFILE");
+
+ if (! profile_name)
+ profile_name = "default";
+
+ return talloc_asprintf (ctx,
+ "%s/notmuch/%s",
+ xdg_root,
+ profile_name);
+}
+
+static notmuch_status_t
+_choose_dir (notmuch_database_t *notmuch,
+ const char *profile,
+ notmuch_config_key_t key,
+ const char *xdg_var,
+ const char *xdg_subdir,
+ const char *subdir,
+ char **message = NULL)
+{
+ const char *parent;
+ const char *dir;
+ struct stat st;
+ int err;
+
+ dir = notmuch_config_get (notmuch, key);
+
+ if (dir)
+ return NOTMUCH_STATUS_SUCCESS;
+
+ parent = _xdg_dir (notmuch, xdg_var, xdg_subdir, profile);
+ if (! parent)
+ return NOTMUCH_STATUS_PATH_ERROR;
+
+ dir = talloc_asprintf (notmuch, "%s/%s", parent, subdir);
+
+ err = stat (dir, &st);
+ if (err) {
+ if (errno == ENOENT) {
+ char *notmuch_path = dirname (talloc_strdup (notmuch, notmuch->xapian_path));
+ dir = talloc_asprintf (notmuch, "%s/%s", notmuch_path, subdir);
+ } else {
+ IGNORE_RESULT (asprintf (message, "Error: Cannot stat %s: %s.\n",
+ dir, strerror (errno)));
+ return NOTMUCH_STATUS_FILE_ERROR;
+ }
+ }
+
+ _notmuch_config_cache (notmuch, key, dir);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_load_key_file (notmuch_database_t *notmuch,
+ const char *path,
+ const char *profile,
+ GKeyFile **key_file)
+{
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+
+ if (path && EMPTY_STRING (path))
+ goto DONE;
+
+ if (! path)
+ path = getenv ("NOTMUCH_CONFIG");
+
+ if (path)
+ path = talloc_strdup (notmuch, path);
+ else {
+ const char *dir = _xdg_dir (notmuch, "XDG_CONFIG_HOME", ".config", profile);
+
+ if (dir) {
+ path = talloc_asprintf (notmuch, "%s/config", dir);
+ if (access (path, R_OK) != 0)
+ path = NULL;
+ }
+ }
+
+ if (! path) {
+ const char *home = getenv ("HOME");
+
+ path = talloc_asprintf (notmuch, "%s/.notmuch-config", home);
+
+ if (! profile)
+ profile = getenv ("NOTMUCH_PROFILE");
+
+ if (profile)
+ path = talloc_asprintf (notmuch, "%s.%s", path, profile);
+ }
+
+ *key_file = g_key_file_new ();
+ if (! g_key_file_load_from_file (*key_file, path, G_KEY_FILE_NONE, NULL)) {
+ status = NOTMUCH_STATUS_NO_CONFIG;
+ }
+
+ DONE:
+ if (path)
+ notmuch->config_path = path;
+
+ return status;
+}
+
+static notmuch_status_t
+_db_dir_exists (const char *database_path, char **message)
+{
+ struct stat st;
+ int err;
+
+ err = stat (database_path, &st);
+ if (err) {
+ IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: %s.\n",
+ database_path, strerror (errno)));
+ return NOTMUCH_STATUS_FILE_ERROR;
+ }
+
+ if (! S_ISDIR (st.st_mode)) {
+ IGNORE_RESULT (asprintf (message, "Error: Cannot open database at %s: "
+ "Not a directory.\n",
+ database_path));
+ return NOTMUCH_STATUS_FILE_ERROR;
+ }
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_choose_database_path (void *ctx,
+ const char *profile,
+ GKeyFile *key_file,
+ const char **database_path,
+ bool *split,
+ char **message)
+{
+ if (! *database_path) {
+ *database_path = getenv ("NOTMUCH_DATABASE");
+ }
+
+ if (! *database_path && key_file) {
+ char *path = g_key_file_get_value (key_file, "database", "path", NULL);
+ if (path) {
+ if (path[0] == '/')
+ *database_path = talloc_strdup (ctx, path);
+ else
+ *database_path = talloc_asprintf (ctx, "%s/%s", getenv ("HOME"), path);
+ g_free (path);
+ }
+ }
+ if (! *database_path) {
+ notmuch_status_t status;
+
+ *database_path = _xdg_dir (ctx, "XDG_DATA_HOME", ".local/share", profile);
+ status = _db_dir_exists (*database_path, message);
+ if (status) {
+ *database_path = NULL;
+ } else {
+ *split = true;
+ }
+ }
+
+ if (! *database_path) {
+ *database_path = getenv ("MAILDIR");
+ }
+
+ if (! *database_path) {
+ notmuch_status_t status;
+
+ *database_path = talloc_asprintf (ctx, "%s/mail", getenv ("HOME"));
+ status = _db_dir_exists (*database_path, message);
+ if (status) {
+ *database_path = NULL;
+ }
+ }
+
+ if (*database_path == NULL) {
+ *message = strdup ("Error: could not locate database.\n");
+ return NOTMUCH_STATUS_NO_DATABASE;
+ }
+
+ if (*database_path[0] != '/') {
+ *message = strdup ("Error: Database path must be absolute.\n");
+ return NOTMUCH_STATUS_PATH_ERROR;
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_database_t *
+_alloc_notmuch ()
+{
+ notmuch_database_t *notmuch;
+
+ notmuch = talloc_zero (NULL, notmuch_database_t);
+ if (! notmuch)
+ return NULL;
+
+ notmuch->exception_reported = false;
+ notmuch->status_string = NULL;
+ notmuch->writable_xapian_db = NULL;
+ notmuch->config_path = NULL;
+ notmuch->atomic_nesting = 0;
+ notmuch->view = 1;
+ return notmuch;
+}
+
+static notmuch_status_t
+_trial_open (const char *xapian_path, char **message_ptr)
+{
+ try {
+ Xapian::Database db (xapian_path);
+ } catch (const Xapian::DatabaseOpeningError &error) {
+ IGNORE_RESULT (asprintf (message_ptr,
+ "Cannot open Xapian database at %s: %s\n",
+ xapian_path,
+ error.get_msg ().c_str ()));
+ return NOTMUCH_STATUS_PATH_ERROR;
+ } catch (const Xapian::Error &error) {
+ IGNORE_RESULT (asprintf (message_ptr,
+ "A Xapian exception occurred opening database: %s\n",
+ error.get_msg ().c_str ()));
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+_notmuch_choose_xapian_path (void *ctx, const char *database_path,
+ const char **xapian_path, char **message_ptr)
+{
+ notmuch_status_t status;
+ const char *trial_path, *notmuch_path;
+
+ status = _db_dir_exists (database_path, message_ptr);
+ if (status)
+ goto DONE;
+
+ trial_path = talloc_asprintf (ctx, "%s/xapian", database_path);
+ status = _trial_open (trial_path, message_ptr);
+ if (status != NOTMUCH_STATUS_PATH_ERROR)
+ goto DONE;
+
+ if (*message_ptr)
+ free (*message_ptr);
+
+ notmuch_path = talloc_asprintf (ctx, "%s/.notmuch", database_path);
+ status = _db_dir_exists (notmuch_path, message_ptr);
+ if (status)
+ goto DONE;
+
+ trial_path = talloc_asprintf (ctx, "%s/xapian", notmuch_path);
+ status = _trial_open (trial_path, message_ptr);
+
+ DONE:
+ if (status == NOTMUCH_STATUS_SUCCESS)
+ *xapian_path = trial_path;
+ return status;
+}
+
+static void
+_set_database_path (notmuch_database_t *notmuch,
+ const char *database_path)
+{
+ char *path = talloc_strdup (notmuch, database_path);
+
+ strip_trailing (path, '/');
+
+ _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
+}
+
+static void
+_init_libs ()
+{
+
+ static int initialized = 0;
+
+ /* Initialize the GLib type system and threads */
+#if ! GLIB_CHECK_VERSION (2, 35, 1)
+ g_type_init ();
+#endif
+
+ /* Initialize gmime */
+ if (! initialized) {
+ g_mime_init ();
+ initialized = 1;
+ }
+}
+
+static void
+_load_database_state (notmuch_database_t *notmuch)
+{
+ std::string last_thread_id;
+ std::string last_mod;
+
+ notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
+ last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
+ if (last_thread_id.empty ()) {
+ notmuch->last_thread_id = 0;
+ } else {
+ const char *str;
+ char *end;
+
+ str = last_thread_id.c_str ();
+ notmuch->last_thread_id = strtoull (str, &end, 16);
+ if (*end != '\0')
+ INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
+ }
+
+ /* Get current highest revision number. */
+ last_mod = notmuch->xapian_db->get_value_upper_bound (
+ NOTMUCH_VALUE_LAST_MOD);
+ if (last_mod.empty ())
+ notmuch->revision = 0;
+ else
+ notmuch->revision = Xapian::sortable_unserialise (last_mod);
+ notmuch->uuid = talloc_strdup (
+ notmuch, notmuch->xapian_db->get_uuid ().c_str ());
+}
+
+static notmuch_status_t
+_finish_open (notmuch_database_t *notmuch,
+ const char *profile,
+ notmuch_database_mode_t mode,
+ GKeyFile *key_file,
+ char **message_ptr)
+{
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+ char *incompat_features;
+ char *message = NULL;
+ unsigned int version;
+ const char *database_path = notmuch_database_get_path (notmuch);
+
+ try {
+
+ if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
+ notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
+ DB_ACTION);
+ notmuch->xapian_db = notmuch->writable_xapian_db;
+ } else {
+ notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path);
+ }
+
+ /* Check version. As of database version 3, we represent
+ * changes in terms of features, so assume a version bump
+ * means a dramatically incompatible change. */
+ version = notmuch_database_get_version (notmuch);
+ if (version > NOTMUCH_DATABASE_VERSION) {
+ IGNORE_RESULT (asprintf (&message,
+ "Error: Notmuch database at %s\n"
+ " has a newer database format version (%u) than supported by this\n"
+ " version of notmuch (%u).\n",
+ database_path, version, NOTMUCH_DATABASE_VERSION));
+ notmuch_database_destroy (notmuch);
+ notmuch = NULL;
+ status = NOTMUCH_STATUS_FILE_ERROR;
+ goto DONE;
+ }
+
+ /* Check features. */
+ incompat_features = NULL;
+ notmuch->features = _notmuch_database_parse_features (
+ notmuch, notmuch->xapian_db->get_metadata ("features").c_str (),
+ version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
+ &incompat_features);
+ if (incompat_features) {
+ IGNORE_RESULT (asprintf (&message,
+ "Error: Notmuch database at %s\n"
+ " requires features (%s)\n"
+ " not supported by this version of notmuch.\n",
+ database_path, incompat_features));
+ notmuch_database_destroy (notmuch);
+ notmuch = NULL;
+ status = NOTMUCH_STATUS_FILE_ERROR;
+ goto DONE;
+ }
+
+ _load_database_state (notmuch);
+
+ notmuch->query_parser = new Xapian::QueryParser;
+ notmuch->term_gen = new Xapian::TermGenerator;
+ notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
+ notmuch->value_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+ notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP,
+ "date:");
+ notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (NOTMUCH_VALUE_LAST_MOD,
+ "lastmod:");
+ notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
+ notmuch->query_parser->set_database (*notmuch->xapian_db);
+ notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
+ notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
+ notmuch->query_parser->add_rangeprocessor (notmuch->value_range_processor);
+ notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
+ notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
+
+ /* Configuration information is needed to set up query parser */
+ status = _notmuch_config_load_from_database (notmuch);
+ if (status)
+ goto DONE;
+
+ if (key_file)
+ status = _notmuch_config_load_from_file (notmuch, key_file);
+ if (status)
+ goto DONE;
+
+ status = _choose_dir (notmuch, profile,
+ NOTMUCH_CONFIG_HOOK_DIR,
+ "XDG_CONFIG_HOME",
+ ".config",
+ "hooks",
+ &message);
+ if (status)
+ goto DONE;
+
+ status = _choose_dir (notmuch, profile,
+ NOTMUCH_CONFIG_BACKUP_DIR,
+ "XDG_DATA_HOME",
+ ".local/share",
+ "backups",
+ &message);
+ if (status)
+ goto DONE;
+ status = _notmuch_config_load_defaults (notmuch);
+ if (status)
+ goto DONE;
+
+ status = _notmuch_database_setup_standard_query_fields (notmuch);
+ if (status)
+ goto DONE;
+
+ status = _notmuch_database_setup_user_query_fields (notmuch);
+ if (status)
+ goto DONE;
+
+ } catch (const Xapian::Error &error) {
+ IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
+ error.get_msg ().c_str ()));
+ notmuch_database_destroy (notmuch);
+ notmuch = NULL;
+ status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+ DONE:
+ if (message_ptr)
+ *message_ptr = message;
+ return status;
+}
+
+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 **status_string)
+{
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+ void *local = talloc_new (NULL);
+ notmuch_database_t *notmuch = NULL;
+ char *message = NULL;
+ GKeyFile *key_file = NULL;
+ bool split = false;
+
+ _init_libs ();
+
+ notmuch = _alloc_notmuch ();
+ if (! notmuch) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ status = _load_key_file (notmuch, config_path, profile, &key_file);
+ if (status) {
+ message = strdup ("Error: cannot load config file.\n");
+ goto DONE;
+ }
+
+ if ((status = _choose_database_path (local, profile, key_file,
+ &database_path, &split,
+ &message)))
+ goto DONE;
+
+ status = _db_dir_exists (database_path, &message);
+ if (status)
+ goto DONE;
+
+ _set_database_path (notmuch, database_path);
+
+ status = _notmuch_choose_xapian_path (notmuch, database_path,
+ ¬much->xapian_path, &message);
+ if (status)
+ goto DONE;
+
+ status = _finish_open (notmuch, profile, mode, key_file, &message);
+
+ DONE:
+ talloc_free (local);
+
+ if (key_file)
+ g_key_file_free (key_file);
+
+ if (message) {
+ if (status_string)
+ *status_string = message;
+ else
+ free (message);
+ }
+
+ if (database)
+ *database = notmuch;
+ else
+ talloc_free (notmuch);
+
+ if (notmuch)
+ notmuch->open = true;
+
+ return status;
+}
+
+notmuch_status_t
+notmuch_database_create (const char *path, notmuch_database_t **database)
+{
+ char *status_string = NULL;
+ notmuch_status_t status;
+
+ status = notmuch_database_create_verbose (path, database,
+ &status_string);
+
+ if (status_string) {
+ fputs (status_string, stderr);
+ free (status_string);
+ }
+
+ return status;
+}
+
+notmuch_status_t
+notmuch_database_create_verbose (const char *path,
+ notmuch_database_t **database,
+ char **status_string)
+{
+ return notmuch_database_create_with_config (path, "", NULL, database, status_string);
+}
+
+notmuch_status_t
+notmuch_database_create_with_config (const char *database_path,
+ const char *config_path,
+ const char *profile,
+ notmuch_database_t **database,
+ char **status_string)
+{
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
+ notmuch_database_t *notmuch = NULL;
+ const char *notmuch_path = NULL;
+ char *message = NULL;
+ GKeyFile *key_file = NULL;
+ void *local = talloc_new (NULL);
+ int err;
+ bool split = false;
+
+ _init_libs ();
+
+ notmuch = _alloc_notmuch ();
+ if (! notmuch) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ status = _load_key_file (notmuch, config_path, profile, &key_file);
+ if (status) {
+ message = strdup ("Error: cannot load config file.\n");
+ goto DONE;
+ }
+
+ if ((status = _choose_database_path (local, profile, key_file,
+ &database_path, &split, &message)))
+ goto DONE;
+
+ status = _db_dir_exists (database_path, &message);
+ if (status)
+ goto DONE;
+
+ _set_database_path (notmuch, database_path);
+
+ if (key_file && ! split) {
+ char *mail_root = notmuch_canonicalize_file_name (
+ g_key_file_get_value (key_file, "database", "mail_root", NULL));
+ char *db_path = notmuch_canonicalize_file_name (database_path);
+
+ split = (mail_root && (0 != strcmp (mail_root, db_path)));
+
+ free (mail_root);
+ free (db_path);
+ }
+
+ if (split) {
+ notmuch_path = database_path;
+ } else {
+ if (! (notmuch_path = talloc_asprintf (local, "%s/%s", database_path, ".notmuch"))) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ err = mkdir (notmuch_path, 0755);
+ if (err) {
+ if (errno == EEXIST) {
+ status = NOTMUCH_STATUS_DATABASE_EXISTS;
+ talloc_free (notmuch);
+ notmuch = NULL;
+ } else {
+ IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
+ notmuch_path, strerror (errno)));
+ status = NOTMUCH_STATUS_FILE_ERROR;
+ }
+ goto DONE;
+ }
+ }
+
+ if (! (notmuch->xapian_path = talloc_asprintf (notmuch, "%s/%s", notmuch_path, "xapian"))) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ status = _trial_open (notmuch->xapian_path, &message);
+ if (status == NOTMUCH_STATUS_SUCCESS) {
+ notmuch_database_destroy (notmuch);
+ notmuch = NULL;
+ status = NOTMUCH_STATUS_DATABASE_EXISTS;
+ goto DONE;
+ }
+
+ if (message)
+ free (message);
+
+ status = _finish_open (notmuch,
+ profile,
+ NOTMUCH_DATABASE_MODE_READ_WRITE,
+ key_file,
+ &message);
+ if (status)
+ goto DONE;
+
+ /* Upgrade doesn't add these feature to existing databases, but
+ * new databases have them. */
+ notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
+ notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
+ notmuch->features |= NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY;
+
+ status = notmuch_database_upgrade (notmuch, NULL, NULL);
+ if (status) {
+ notmuch_database_close (notmuch);
+ notmuch = NULL;
+ }
+
+ DONE:
+ talloc_free (local);
+
+ if (key_file)
+ g_key_file_free (key_file);
+
+ if (message) {
+ if (status_string)
+ *status_string = message;
+ else
+ free (message);
+ }
+ if (database)
+ *database = notmuch;
+ else
+ talloc_free (notmuch);
+ return status;
+}
+
+notmuch_status_t
+notmuch_database_reopen (notmuch_database_t *notmuch,
+ notmuch_database_mode_t new_mode)
+{
+ notmuch_database_mode_t cur_mode = _notmuch_database_mode (notmuch);
+
+ if (notmuch->xapian_db == NULL) {
+ _notmuch_database_log (notmuch, "Cannot reopen closed or nonexistent database\n");
+ return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
+ }
+
+ try {
+ if (cur_mode == new_mode &&
+ new_mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
+ notmuch->xapian_db->reopen ();
+ } else {
+ notmuch->xapian_db->close ();
+
+ delete notmuch->xapian_db;
+ notmuch->xapian_db = NULL;
+ /* no need to free the same object twice */
+ notmuch->writable_xapian_db = NULL;
+
+ if (new_mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
+ notmuch->writable_xapian_db = new Xapian::WritableDatabase (notmuch->xapian_path,
+ DB_ACTION);
+ notmuch->xapian_db = notmuch->writable_xapian_db;
+ } else {
+ notmuch->xapian_db = new Xapian::Database (notmuch->xapian_path,
+ DB_ACTION);
+ }
+ }
+
+ _load_database_state (notmuch);
+ } catch (const Xapian::Error &error) {
+ if (! notmuch->exception_reported) {
+ _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
+ error.get_msg ().c_str ());
+ notmuch->exception_reported = true;
+ }
+ return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ }
+
+ notmuch->view++;
+ notmuch->open = true;
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+_maybe_load_config_from_database (notmuch_database_t *notmuch,
+ GKeyFile *key_file,
+ const char *database_path,
+ const char *profile)
+{
+ char *message; /* ignored */
+
+ if (_db_dir_exists (database_path, &message))
+ return NOTMUCH_STATUS_NO_DATABASE;
+
+ _set_database_path (notmuch, database_path);
+
+ if (_notmuch_choose_xapian_path (notmuch, database_path, ¬much->xapian_path, &message))
+ return NOTMUCH_STATUS_NO_DATABASE;
+
+ (void) _finish_open (notmuch, profile, NOTMUCH_DATABASE_MODE_READ_ONLY, key_file, &message);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+notmuch_database_load_config (const char *database_path,
+ const char *config_path,
+ const char *profile,
+ notmuch_database_t **database,
+ char **status_string)
+{
+ notmuch_status_t status = NOTMUCH_STATUS_SUCCESS, warning = NOTMUCH_STATUS_SUCCESS;
+ void *local = talloc_new (NULL);
+ notmuch_database_t *notmuch = NULL;
+ char *message = NULL;
+ GKeyFile *key_file = NULL;
+ bool split = false;
+
+ _init_libs ();
+
+ notmuch = _alloc_notmuch ();
+ if (! notmuch) {
+ status = NOTMUCH_STATUS_OUT_OF_MEMORY;
+ goto DONE;
+ }
+
+ status = _load_key_file (notmuch, config_path, profile, &key_file);
+ switch (status) {
+ case NOTMUCH_STATUS_SUCCESS:
+ break;
+ case NOTMUCH_STATUS_NO_CONFIG:
+ warning = status;
+ break;
+ default:
+ message = strdup ("Error: cannot load config file.\n");
+ goto DONE;
+ }
+
+ status = _choose_database_path (local, profile, key_file,
+ &database_path, &split, &message);
+ switch (status) {
+ case NOTMUCH_STATUS_NO_DATABASE:
+ case NOTMUCH_STATUS_SUCCESS:
+ if (! warning)
+ warning = status;
+ break;
+ default:
+ goto DONE;
+ }
+
+
+ if (database_path) {
+ status = _maybe_load_config_from_database (notmuch, key_file, database_path, profile);
+ switch (status) {
+ case NOTMUCH_STATUS_NO_DATABASE:
+ case NOTMUCH_STATUS_SUCCESS:
+ if (! warning)
+ warning = status;
+ break;
+ default:
+ goto DONE;
+ }
+ }
+
+ if (key_file) {
+ status = _notmuch_config_load_from_file (notmuch, key_file);
+ if (status)
+ goto DONE;
+ }
+ status = _notmuch_config_load_defaults (notmuch);
+ if (status)
+ goto DONE;
+
+ DONE:
+ talloc_free (local);
+
+ if (status_string)
+ *status_string = message;
+
+ if (database)
+ *database = notmuch;
+
+ if (status)
+ return status;
+ else
+ return warning;
+}
if (time (&now) == (time_t) -1)
throw Xapian::QueryParserError ("unable to get current time");
- if (!begin.empty ()) {
+ if (! begin.empty ()) {
if (parse_time_string (begin.c_str (), &parsed_time, &now, PARSE_TIME_ROUND_DOWN))
throw Xapian::QueryParserError ("Didn't understand date specification '" + begin + "'");
else
from = (double) parsed_time;
}
- if (!end.empty ()) {
+ if (! end.empty ()) {
if (end == "!" && ! begin.empty ())
str = begin;
else
--- /dev/null
+#include "database-private.h"
+#include "query-fp.h"
+#include "thread-fp.h"
+#include "regexp-fields.h"
+#include "parse-time-vrp.h"
+
+typedef struct {
+ const char *name;
+ const char *prefix;
+ notmuch_field_flag_t flags;
+} prefix_t;
+
+/* With these prefix values we follow the conventions published here:
+ *
+ * https://xapian.org/docs/omega/termprefixes.html
+ *
+ * as much as makes sense. Note that I took some liberty in matching
+ * the reserved prefix values to notmuch concepts, (for example, 'G'
+ * is documented as "newsGroup (or similar entity - e.g. a web forum
+ * name)", for which I think the thread is the closest analogue in
+ * notmuch. This in spite of the fact that we will eventually be
+ * storing mailing-list messages where 'G' for "mailing list name"
+ * might be even a closer analogue. I'm treating the single-character
+ * prefixes preferentially for core notmuch concepts (which will be
+ * nearly universal to all mail messages).
+ */
+
+static const
+prefix_t prefix_table[] = {
+ /* name term prefix flags */
+ { "type", "T", NOTMUCH_FIELD_NO_FLAGS },
+ { "reference", "XREFERENCE", NOTMUCH_FIELD_NO_FLAGS },
+ { "replyto", "XREPLYTO", NOTMUCH_FIELD_NO_FLAGS },
+ { "directory", "XDIRECTORY", NOTMUCH_FIELD_NO_FLAGS },
+ { "file-direntry", "XFDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
+ { "directory-direntry", "XDDIRENTRY", NOTMUCH_FIELD_NO_FLAGS },
+ { "body", "", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "thread", "G", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "tag", "K", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "is", "K", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "id", "Q", NOTMUCH_FIELD_EXTERNAL },
+ { "mid", "Q", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "path", "P", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL },
+ /*
+ * Unconditionally add ':' to reduce potential ambiguity with
+ * overlapping prefixes and/or terms that start with capital
+ * letters. See Xapian document termprefixes.html for related
+ * discussion.
+ */
+ { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "date", NULL, NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "query", NULL, NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC |
+ NOTMUCH_FIELD_PROCESSOR },
+ { "to", "XTO", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "attachment", "XATTACHMENT", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "mimetype", "XMIMETYPE", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC },
+ { "subject", "XSUBJECT", NOTMUCH_FIELD_EXTERNAL |
+ NOTMUCH_FIELD_PROBABILISTIC |
+ NOTMUCH_FIELD_PROCESSOR },
+};
+
+static const char *
+_user_prefix (void *ctx, const char *name)
+{
+ return talloc_asprintf (ctx, "XU%s:", name);
+}
+
+const char *
+_find_prefix (const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+ if (strcmp (name, prefix_table[i].name) == 0)
+ return prefix_table[i].prefix;
+ }
+
+ INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
+
+ return "";
+}
+
+/* Like find prefix, but include the possibility of user defined
+ * prefixes specific to this database */
+
+const char *
+_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
+{
+ unsigned int i;
+
+ /*XXX TODO: reduce code duplication */
+ for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+ if (strcmp (name, prefix_table[i].name) == 0)
+ return prefix_table[i].prefix;
+ }
+
+ if (notmuch->user_prefix)
+ return _notmuch_string_map_get (notmuch->user_prefix, name);
+
+ return NULL;
+}
+
+static void
+_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+ if (prefix->prefix)
+ notmuch->query_parser->add_prefix ("", prefix->prefix);
+ if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
+ notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+ else
+ notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
+}
+
+static void
+_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+ if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
+ Xapian::FieldProcessor *fp;
+
+ if (STRNCMP_LITERAL (prefix->name, "date") == 0)
+ fp = (new DateFieldProcessor (NOTMUCH_VALUE_TIMESTAMP))->release ();
+ else if (STRNCMP_LITERAL (prefix->name, "query") == 0)
+ fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+ else if (STRNCMP_LITERAL (prefix->name, "thread") == 0)
+ fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
+ else
+ fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
+ *notmuch->query_parser, notmuch))->release ();
+
+ /* we treat all field-processor fields as boolean in order to get the raw input */
+ if (prefix->prefix)
+ notmuch->query_parser->add_prefix ("", prefix->prefix);
+ notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
+ } else {
+ _setup_query_field_default (prefix, notmuch);
+ }
+}
+
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+ const prefix_t *prefix = &prefix_table[i];
+ if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
+ _setup_query_field (prefix, notmuch);
+ }
+ }
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch)
+{
+ notmuch_string_map_iterator_t *list;
+
+ notmuch->user_prefix = _notmuch_string_map_create (notmuch);
+ if (notmuch->user_prefix == NULL)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ notmuch->user_header = _notmuch_string_map_create (notmuch);
+ if (notmuch->user_header == NULL)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+ list = _notmuch_string_map_iterator_create (notmuch->config, CONFIG_HEADER_PREFIX, FALSE);
+ if (! list)
+ INTERNAL_ERROR ("unable to read headers from configuration");
+
+ for (; _notmuch_string_map_iterator_valid (list);
+ _notmuch_string_map_iterator_move_to_next (list)) {
+
+ prefix_t query_field;
+
+ const char *key = _notmuch_string_map_iterator_key (list)
+ + sizeof (CONFIG_HEADER_PREFIX) - 1;
+
+ _notmuch_string_map_append (notmuch->user_prefix,
+ key,
+ _user_prefix (notmuch, key));
+
+ _notmuch_string_map_append (notmuch->user_header,
+ key,
+ _notmuch_string_map_iterator_value (list));
+
+ query_field.name = talloc_strdup (notmuch, key);
+ query_field.prefix = _user_prefix (notmuch, key);
+ query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
+ | NOTMUCH_FIELD_EXTERNAL;
+
+ _setup_query_field_default (&query_field, notmuch);
+ }
+
+ _notmuch_string_map_iterator_destroy (list);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
}
+void
+_notmuch_string_map_set (notmuch_string_map_t *map,
+ const char *key,
+ const char *val)
+{
+ notmuch_string_pair_t *pair;
+
+ /* this means that calling string_map_set invalidates iterators */
+ _notmuch_string_map_sort (map);
+ pair = bsearch_first (map->pairs, map->length, key, true);
+ if (! pair)
+ _notmuch_string_map_append (map, key, val);
+ else {
+ talloc_free (pair->value);
+ pair->value = talloc_strdup (map->pairs, val);
+ }
+}
+
const char *
_notmuch_string_map_get (notmuch_string_map_t *map, const char *key)
{
std::set<std::string> terms;
if (! subquery)
- throw Xapian::QueryParserError ("failed to create subquery for '" + subquery_str + "'");
+ throw Xapian::QueryParserError ("failed to create subquery for '" + subquery_str +
+ "'");
status = notmuch_query_search_messages (subquery, &messages);
if (status)
- throw Xapian::QueryParserError ("failed to search messages for '" + subquery_str + "'");
+ throw Xapian::QueryParserError ("failed to search messages for '" + subquery_str +
+ "'");
for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) {
std::string term = thread_prefix;
notmuch_messages_valid (roots);
notmuch_messages_move_to_next (roots)) {
notmuch_message_t *message = notmuch_messages_get (roots);
- if (_notmuch_messages_has_next (roots) || ! _notmuch_message_list_empty (thread->toplevel_list))
+ if (_notmuch_messages_has_next (roots) || ! _notmuch_message_list_empty (
+ thread->toplevel_list))
_parent_or_toplevel (thread, message);
else
_notmuch_message_list_add_message (thread->toplevel_list, message);
status = _notmuch_message_crypto_potential_sig_list (node->ctx->msg_crypto, node->sig_list);
if (status) /* this is a warning, not an error */
- fprintf (stderr, "Warning: failed to note signature status: %s.\n", notmuch_status_to_string (status));
+ fprintf (stderr, "Warning: failed to note signature status: %s.\n", notmuch_status_to_string (
+ status));
}
/* Decrypt and optionally verify an encrypted mime node */
node->decrypt_success = true;
status = _notmuch_message_crypto_successful_decryption (node->ctx->msg_crypto);
if (status) /* this is a warning, not an error */
- fprintf (stderr, "Warning: failed to note decryption status: %s.\n", notmuch_status_to_string (status));
+ fprintf (stderr, "Warning: failed to note decryption status: %s.\n",
+ notmuch_status_to_string (status));
if (decrypt_result) {
/* This may be NULL if the part is not signed. */
node->verify_attempted = true;
g_object_ref (node->sig_list);
set_signature_list_destructor (node);
- status = _notmuch_message_crypto_potential_sig_list (node->ctx->msg_crypto, node->sig_list);
+ status = _notmuch_message_crypto_potential_sig_list (node->ctx->msg_crypto,
+ node->sig_list);
if (status) /* this is a warning, not an error */
- fprintf (stderr, "Warning: failed to note signature status: %s.\n", notmuch_status_to_string (status));
+ fprintf (stderr, "Warning: failed to note signature status: %s.\n",
+ notmuch_status_to_string (status));
}
if (node->ctx->crypto->decrypt == NOTMUCH_DECRYPT_TRUE && message) {
}
/* Handle PGP/MIME parts (by definition not cryptographic payload parts) */
- if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) {
+ if (GMIME_IS_MULTIPART_ENCRYPTED (part) && (node->ctx->crypto->decrypt !=
+ NOTMUCH_DECRYPT_FALSE)) {
if (node->nchildren != 2) {
/* this violates RFC 3156 section 4, so we won't bother with it. */
fprintf (stderr, "Error: %d part(s) for a multipart/encrypted "
node_verify (node, part);
}
} else if (GMIME_IS_APPLICATION_PKCS7_MIME (part) &&
- GMIME_SECURE_MIME_TYPE_SIGNED_DATA == g_mime_application_pkcs7_mime_get_smime_type (GMIME_APPLICATION_PKCS7_MIME (part))) {
+ GMIME_SECURE_MIME_TYPE_SIGNED_DATA == g_mime_application_pkcs7_mime_get_smime_type (
+ GMIME_APPLICATION_PKCS7_MIME (part))) {
/* If node->ctx->crypto->verify is false, it would be better
* to just unwrap (instead of verifying), but
* https://github.com/jstedfast/gmime/issues/67 */
node_verify (node, part);
} else if (GMIME_IS_APPLICATION_PKCS7_MIME (part) &&
- GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA == g_mime_application_pkcs7_mime_get_smime_type (GMIME_APPLICATION_PKCS7_MIME (part)) &&
+ GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA == g_mime_application_pkcs7_mime_get_smime_type (
+ GMIME_APPLICATION_PKCS7_MIME (part)) &&
(node->ctx->crypto->decrypt != NOTMUCH_DECRYPT_FALSE)) {
node_decrypt_and_verify (node, part);
if (node->unwrapped_child && node->nchildren == 0)
node->nchildren = 1;
} else {
- if (_notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ? node->parent->part : NULL, numchild) &&
+ if (_notmuch_message_crypto_potential_payload (node->ctx->msg_crypto, part, node->parent ?
+ node->parent->part : NULL, numchild) &&
node->ctx->msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL) {
GMimeObject *clean_payload = _notmuch_repair_crypto_payload_skip_legacy_display (part);
if (clean_payload != part) {
*/
extern int notmuch_format_version;
-typedef struct _notmuch_config notmuch_config_t;
+typedef struct _notmuch_conffile notmuch_conffile_t;
/* Commands that support structured output should support the
* following argument
notmuch_exit_if_unsupported_format (void);
int
-notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_count_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_dump_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_reply_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_restore_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_search_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_search_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_address_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_address_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_setup_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_setup_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_tag_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_config_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[]);
int
-notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_compact_command (notmuch_database_t *notmuch, int argc, char *argv[]);
const char *
notmuch_time_relative_date (const void *ctx, time_t then);
/* notmuch-config.c */
typedef enum {
- NOTMUCH_CONFIG_OPEN = 1 << 0,
- NOTMUCH_CONFIG_CREATE = 1 << 1,
-} notmuch_config_mode_t;
-
-notmuch_config_t *
-notmuch_config_open (void *ctx,
- const char *filename,
- notmuch_config_mode_t config_mode);
+ NOTMUCH_COMMAND_CONFIG_CREATE = 1 << 1,
+ NOTMUCH_COMMAND_DATABASE_EARLY = 1 << 2,
+ NOTMUCH_COMMAND_DATABASE_WRITE = 1 << 3,
+ NOTMUCH_COMMAND_DATABASE_CREATE = 1 << 4,
+ NOTMUCH_COMMAND_CONFIG_LOAD = 1 << 5,
+} notmuch_command_mode_t;
+
+notmuch_conffile_t *
+notmuch_conffile_open (notmuch_database_t *notmuch,
+ const char *filename,
+ bool create);
void
-notmuch_config_close (notmuch_config_t *config);
+notmuch_conffile_close (notmuch_conffile_t *config);
int
-notmuch_config_save (notmuch_config_t *config);
+notmuch_conffile_save (notmuch_conffile_t *config);
bool
-notmuch_config_is_new (notmuch_config_t *config);
-
-const char *
-notmuch_config_get_database_path (notmuch_config_t *config);
+notmuch_conffile_is_new (notmuch_conffile_t *config);
void
-notmuch_config_set_database_path (notmuch_config_t *config,
- const char *database_path);
-
-const char *
-notmuch_config_get_user_name (notmuch_config_t *config);
+notmuch_conffile_set_database_path (notmuch_conffile_t *config,
+ const char *database_path);
void
-notmuch_config_set_user_name (notmuch_config_t *config,
- const char *user_name);
-
-const char *
-notmuch_config_get_user_primary_email (notmuch_config_t *config);
+notmuch_conffile_set_user_name (notmuch_conffile_t *config,
+ const char *user_name);
void
-notmuch_config_set_user_primary_email (notmuch_config_t *config,
- const char *primary_email);
-
-const char **
-notmuch_config_get_user_other_email (notmuch_config_t *config,
- size_t *length);
+notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
+ const char *primary_email);
void
-notmuch_config_set_user_other_email (notmuch_config_t *config,
- const char *other_email[],
- size_t length);
+notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
+ const char *other_email[],
+ size_t length);
-const char **
-notmuch_config_get_new_tags (notmuch_config_t *config,
- size_t *length);
void
-notmuch_config_set_new_tags (notmuch_config_t *config,
- const char *new_tags[],
- size_t length);
-
-const char **
-notmuch_config_get_new_ignore (notmuch_config_t *config,
- size_t *length);
-
-void
-notmuch_config_set_new_ignore (notmuch_config_t *config,
- const char *new_ignore[],
+notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
+ const char *new_tags[],
size_t length);
-bool
-notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config);
-
void
-notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
- bool synchronize_flags);
-
-const char **
-notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length);
+notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
+ const char *new_ignore[],
+ size_t length);
void
-notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
- const char *list[],
- size_t length);
+notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
+ bool synchronize_flags);
+void
+notmuch_conffile_set_search_exclude_tags (notmuch_conffile_t *config,
+ const char *list[],
+ size_t length);
int
-notmuch_run_hook (const char *db_path, const char *hook);
+notmuch_run_hook (notmuch_database_t *notmuch, const char *hook);
bool
debugger_is_active (void);
int bytes);
/* the __location__ macro is defined in talloc.h */
-#define ASSERT_GZBYTES(file, bytes) ((print_status_gzbytes (__location__, file, bytes)) ? exit (1) : 0)
+#define ASSERT_GZBYTES(file, bytes) ((print_status_gzbytes (__location__, file, bytes)) ? exit (1) : \
+ 0)
#define GZPRINTF(file, fmt, ...) ASSERT_GZBYTES (file, gzprintf (file, fmt, ##__VA_ARGS__));
-#define GZPUTS(file, str) ASSERT_GZBYTES(file, gzputs (file, str));
+#define GZPUTS(file, str) ASSERT_GZBYTES (file, gzputs (file, str));
#include "command-line-arguments.h"
}
int
-notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_compact_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- const char *path = notmuch_config_get_database_path (config);
const char *backup_path = NULL;
notmuch_status_t ret;
bool quiet = false;
if (! quiet)
printf ("Compacting database...\n");
- ret = notmuch_database_compact (path, backup_path,
- quiet ? NULL : status_update_cb, NULL);
+ ret = notmuch_database_compact_db (notmuch, backup_path,
+ quiet ? NULL : status_update_cb, NULL);
if (ret) {
fprintf (stderr, "Compaction failed: %s\n", notmuch_status_to_string (ret));
return EXIT_FAILURE;
#include <netdb.h>
#include <assert.h>
+#include "path-util.h"
#include "unicode-util.h"
static const char toplevel_config_comment[] =
"\n"
" For more information about notmuch, see https://notmuchmail.org";
-static const char database_config_comment[] =
- " Database configuration\n"
- "\n"
- " The only value supported here is 'path' which should be the top-level\n"
- " directory where your mail currently exists and to where mail will be\n"
- " delivered in the future. Files should be individual email messages.\n"
- " Notmuch will store its database within a sub-directory of the path\n"
- " configured here named \".notmuch\".\n";
-
-static const char new_config_comment[] =
- " Configuration for \"notmuch new\"\n"
- "\n"
- " The following options are supported here:\n"
- "\n"
- "\ttags A list (separated by ';') of the tags that will be\n"
- "\t added to all messages incorporated by \"notmuch new\".\n"
- "\n"
- "\tignore A list (separated by ';') of file and directory names\n"
- "\t that will not be searched for messages by \"notmuch new\".\n"
- "\n"
- "\t NOTE: *Every* file/directory that goes by one of those\n"
- "\t names will be ignored, independent of its depth/location\n"
- "\t in the mail store.\n";
-
-static const char user_config_comment[] =
- " User configuration\n"
- "\n"
- " Here is where you can let notmuch know how you would like to be\n"
- " addressed. Valid settings are\n"
- "\n"
- "\tname Your full name.\n"
- "\tprimary_email Your primary email address.\n"
- "\tother_email A list (separated by ';') of other email addresses\n"
- "\t at which you receive email.\n"
- "\n"
- " Notmuch will use the various email addresses configured here when\n"
- " formatting replies. It will avoid including your own addresses in the\n"
- " recipient list of replies, and will set the From address based on the\n"
- " address to which the original email was addressed.\n";
-
-static const char maildir_config_comment[] =
- " Maildir compatibility configuration\n"
- "\n"
- " The following option is supported here:\n"
- "\n"
- "\tsynchronize_flags Valid values are true and false.\n"
- "\n"
- "\tIf true, then the following maildir flags (in message filenames)\n"
- "\twill be synchronized with the corresponding notmuch tags:\n"
- "\n"
- "\t\tFlag Tag\n"
- "\t\t---- -------\n"
- "\t\tD draft\n"
- "\t\tF flagged\n"
- "\t\tP passed\n"
- "\t\tR replied\n"
- "\t\tS unread (added when 'S' flag is not present)\n"
- "\n"
- "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
- "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
- "\tcommands will notice tag changes and update flags in filenames\n";
-
-static const char search_config_comment[] =
- " Search configuration\n"
- "\n"
- " The following option is supported here:\n"
- "\n"
- "\texclude_tags\n"
- "\t\tA ;-separated list of tags that will be excluded from\n"
- "\t\tsearch results by default. Using an excluded tag in a\n"
- "\t\tquery will override that exclusion.\n";
-
-static const char crypto_config_comment[] =
- " Cryptography related configuration\n"
- "\n"
- " The following old option is now ignored:\n"
- "\n"
- "\tgpgpath\n"
- "\t\tThis option was used by older builds of notmuch to choose\n"
- "\t\tthe version of gpg to use.\n"
- "\t\tSetting $PATH is a better approach.\n";
+struct config_group {
+ const char *group_name;
+ const char *comment;
+} group_comment_table [] = {
+ {
+ "database",
+ " Database configuration\n"
+ "\n"
+ " The only value supported here is 'path' which should be the top-level\n"
+ " directory where your mail currently exists and to where mail will be\n"
+ " delivered in the future. Files should be individual email messages.\n"
+ " Notmuch will store its database within a sub-directory of the path\n"
+ " configured here named \".notmuch\".\n"
+ },
+ {
+ "user",
+ " User configuration\n"
+ "\n"
+ " Here is where you can let notmuch know how you would like to be\n"
+ " addressed. Valid settings are\n"
+ "\n"
+ "\tname Your full name.\n"
+ "\tprimary_email Your primary email address.\n"
+ "\tother_email A list (separated by ';') of other email addresses\n"
+ "\t at which you receive email.\n"
+ "\n"
+ " Notmuch will use the various email addresses configured here when\n"
+ " formatting replies. It will avoid including your own addresses in the\n"
+ " recipient list of replies, and will set the From address based on the\n"
+ " address to which the original email was addressed.\n"
+ },
+ {
+ "new",
+ " Configuration for \"notmuch new\"\n"
+ "\n"
+ " The following options are supported here:\n"
+ "\n"
+ "\ttags A list (separated by ';') of the tags that will be\n"
+ "\t added to all messages incorporated by \"notmuch new\".\n"
+ "\n"
+ "\tignore A list (separated by ';') of file and directory names\n"
+ "\t that will not be searched for messages by \"notmuch new\".\n"
+ "\n"
+ "\t NOTE: *Every* file/directory that goes by one of those\n"
+ "\t names will be ignored, independent of its depth/location\n"
+ "\t in the mail store.\n"
+ },
+ {
+ "search",
+ " Search configuration\n"
+ "\n"
+ " The following option is supported here:\n"
+ "\n"
+ "\texclude_tags\n"
+ "\t\tA ;-separated list of tags that will be excluded from\n"
+ "\t\tsearch results by default. Using an excluded tag in a\n"
+ "\t\tquery will override that exclusion.\n"
+ },
+ {
+ "maildir",
+ " Maildir compatibility configuration\n"
+ "\n"
+ " The following option is supported here:\n"
+ "\n"
+ "\tsynchronize_flags Valid values are true and false.\n"
+ "\n"
+ "\tIf true, then the following maildir flags (in message filenames)\n"
+ "\twill be synchronized with the corresponding notmuch tags:\n"
+ "\n"
+ "\t\tFlag Tag\n"
+ "\t\t---- -------\n"
+ "\t\tD draft\n"
+ "\t\tF flagged\n"
+ "\t\tP passed\n"
+ "\t\tR replied\n"
+ "\t\tS unread (added when 'S' flag is not present)\n"
+ "\n"
+ "\tThe \"notmuch new\" command will notice flag changes in filenames\n"
+ "\tand update tags, while the \"notmuch tag\" and \"notmuch restore\"\n"
+ "\tcommands will notice tag changes and update flags in filenames\n"
+ },
+};
-struct _notmuch_config {
+struct _notmuch_conffile {
char *filename;
GKeyFile *key_file;
bool is_new;
-
- char *database_path;
- char *user_name;
- char *user_primary_email;
- const char **user_other_email;
- size_t user_other_email_length;
- const char **new_tags;
- size_t new_tags_length;
- const char **new_ignore;
- size_t new_ignore_length;
- bool maildir_synchronize_flags;
- const char **search_exclude_tags;
- size_t search_exclude_tags_length;
};
static int
-notmuch_config_destructor (notmuch_config_t *config)
+notmuch_conffile_destructor (notmuch_conffile_t *config)
{
if (config->key_file)
g_key_file_free (config->key_file);
return 0;
}
-static char *
-get_name_from_passwd_file (void *ctx)
-{
- long pw_buf_size;
- char *pw_buf;
- struct passwd passwd, *ignored;
- char *name;
- int e;
-
- pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
- if (pw_buf_size == -1) pw_buf_size = 64;
- pw_buf = talloc_size (ctx, pw_buf_size);
-
- while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
- pw_buf_size, &ignored)) == ERANGE) {
- pw_buf_size = pw_buf_size * 2;
- pw_buf = talloc_zero_size (ctx, pw_buf_size);
- }
-
- if (e == 0) {
- char *comma = strchr (passwd.pw_gecos, ',');
- if (comma)
- name = talloc_strndup (ctx, passwd.pw_gecos,
- comma - passwd.pw_gecos);
- else
- name = talloc_strdup (ctx, passwd.pw_gecos);
- } else {
- name = talloc_strdup (ctx, "");
- }
-
- talloc_free (pw_buf);
-
- return name;
-}
-
-static char *
-get_username_from_passwd_file (void *ctx)
-{
- long pw_buf_size;
- char *pw_buf;
- struct passwd passwd, *ignored;
- char *name;
- int e;
-
- pw_buf_size = sysconf (_SC_GETPW_R_SIZE_MAX);
- if (pw_buf_size == -1) pw_buf_size = 64;
- pw_buf = talloc_zero_size (ctx, pw_buf_size);
-
- while ((e = getpwuid_r (getuid (), &passwd, pw_buf,
- pw_buf_size, &ignored)) == ERANGE) {
- pw_buf_size = pw_buf_size * 2;
- pw_buf = talloc_zero_size (ctx, pw_buf_size);
- }
-
- if (e == 0)
- name = talloc_strdup (ctx, passwd.pw_name);
- else
- name = talloc_strdup (ctx, "");
-
- talloc_free (pw_buf);
-
- return name;
-}
-
static bool
-get_config_from_file (notmuch_config_t *config, bool create_new)
+get_config_from_file (notmuch_conffile_t *config, bool create_new)
{
#define BUF_SIZE 4096
char *config_str = NULL;
* The default configuration also contains comments to guide the
* user in editing the file directly.
*/
-notmuch_config_t *
-notmuch_config_open (void *ctx,
- const char *filename,
- notmuch_config_mode_t config_mode)
+notmuch_conffile_t *
+notmuch_conffile_open (notmuch_database_t *notmuch,
+ const char *filename,
+ bool create)
{
- GError *error = NULL;
- size_t tmp;
char *notmuch_config_env = NULL;
- int file_had_database_group;
- int file_had_new_group;
- int file_had_user_group;
- int file_had_maildir_group;
- int file_had_search_group;
- int file_had_crypto_group;
- notmuch_config_t *config = talloc_zero (ctx, notmuch_config_t);
+ notmuch_conffile_t *config = talloc_zero (notmuch, notmuch_conffile_t);
if (config == NULL) {
fprintf (stderr, "Out of memory.\n");
return NULL;
}
- talloc_set_destructor (config, notmuch_config_destructor);
-
- /* non-zero defaults */
- config->maildir_synchronize_flags = true;
+ talloc_set_destructor (config, notmuch_conffile_destructor);
if (filename) {
config->filename = talloc_strdup (config, filename);
config->key_file = g_key_file_new ();
- if (config_mode & NOTMUCH_CONFIG_OPEN) {
- bool create_new = (config_mode & NOTMUCH_CONFIG_CREATE) != 0;
-
- if (! get_config_from_file (config, create_new)) {
- talloc_free (config);
- return NULL;
- }
- }
-
- /* Whenever we know of configuration sections that don't appear in
- * the configuration file, we add some comments to help the user
- * understand what can be done.
- *
- * It would be convenient to just add those comments now, but
- * apparently g_key_file will clear any comments when keys are
- * added later that create the groups. So we have to check for the
- * groups now, but add the comments only after setting all of our
- * values.
- */
- file_had_database_group = g_key_file_has_group (config->key_file,
- "database");
- file_had_new_group = g_key_file_has_group (config->key_file, "new");
- file_had_user_group = g_key_file_has_group (config->key_file, "user");
- file_had_maildir_group = g_key_file_has_group (config->key_file, "maildir");
- file_had_search_group = g_key_file_has_group (config->key_file, "search");
- file_had_crypto_group = g_key_file_has_group (config->key_file, "crypto");
-
- if (notmuch_config_get_database_path (config) == NULL) {
- char *path = getenv ("MAILDIR");
- if (path)
- path = talloc_strdup (config, path);
- else
- path = talloc_asprintf (config, "%s/mail",
- getenv ("HOME"));
- notmuch_config_set_database_path (config, path);
- talloc_free (path);
- }
-
- if (notmuch_config_get_user_name (config) == NULL) {
- char *name = getenv ("NAME");
- if (name)
- name = talloc_strdup (config, name);
- else
- name = get_name_from_passwd_file (config);
- notmuch_config_set_user_name (config, name);
- talloc_free (name);
- }
-
- if (notmuch_config_get_user_primary_email (config) == NULL) {
- char *email = getenv ("EMAIL");
- if (email) {
- notmuch_config_set_user_primary_email (config, email);
- } else {
- char hostname[256];
- struct hostent *hostent;
- const char *domainname;
-
- char *username = get_username_from_passwd_file (config);
-
- gethostname (hostname, 256);
- hostname[255] = '\0';
-
- hostent = gethostbyname (hostname);
- if (hostent && (domainname = strchr (hostent->h_name, '.')))
- domainname += 1;
- else
- domainname = "(none)";
-
- email = talloc_asprintf (config, "%s@%s.%s",
- username, hostname, domainname);
-
- notmuch_config_set_user_primary_email (config, email);
-
- talloc_free (username);
- talloc_free (email);
- }
- }
-
- if (notmuch_config_get_new_tags (config, &tmp) == NULL) {
- const char *tags[] = { "unread", "inbox" };
- notmuch_config_set_new_tags (config, tags, 2);
- }
-
- if (notmuch_config_get_new_ignore (config, &tmp) == NULL) {
- notmuch_config_set_new_ignore (config, NULL, 0);
- }
-
- if (notmuch_config_get_search_exclude_tags (config, &tmp) == NULL) {
- if (config->is_new) {
- const char *tags[] = { "deleted", "spam" };
- notmuch_config_set_search_exclude_tags (config, tags, 2);
- } else {
- notmuch_config_set_search_exclude_tags (config, NULL, 0);
- }
- }
-
- error = NULL;
- config->maildir_synchronize_flags =
- g_key_file_get_boolean (config->key_file,
- "maildir", "synchronize_flags", &error);
- if (error) {
- notmuch_config_set_maildir_synchronize_flags (config, true);
- g_error_free (error);
+ if (! get_config_from_file (config, create)) {
+ talloc_free (config);
+ return NULL;
}
- /* Whenever we know of configuration sections that don't appear in
- * the configuration file, we add some comments to help the user
- * understand what can be done. */
if (config->is_new)
g_key_file_set_comment (config->key_file, NULL, NULL,
toplevel_config_comment, NULL);
- if (! file_had_database_group)
- g_key_file_set_comment (config->key_file, "database", NULL,
- database_config_comment, NULL);
-
- if (! file_had_new_group)
- g_key_file_set_comment (config->key_file, "new", NULL,
- new_config_comment, NULL);
-
- if (! file_had_user_group)
- g_key_file_set_comment (config->key_file, "user", NULL,
- user_config_comment, NULL);
-
- if (! file_had_maildir_group)
- g_key_file_set_comment (config->key_file, "maildir", NULL,
- maildir_config_comment, NULL);
-
- if (! file_had_search_group)
- g_key_file_set_comment (config->key_file, "search", NULL,
- search_config_comment, NULL);
-
- if (! file_had_crypto_group)
- g_key_file_set_comment (config->key_file, "crypto", NULL,
- crypto_config_comment, NULL);
-
+ for (size_t i = 0; i < ARRAY_SIZE (group_comment_table); i++) {
+ const char *name = group_comment_table[i].group_name;
+ if (! g_key_file_has_group (config->key_file, name)) {
+ /* Force group to exist before adding comment */
+ g_key_file_set_value (config->key_file, name, "dummy_key", "dummy_val");
+ g_key_file_remove_key (config->key_file, name, "dummy_key", NULL);
+ g_key_file_set_comment (config->key_file, name, NULL,
+ group_comment_table[i].comment, NULL);
+ }
+ }
return config;
}
-/* Close the given notmuch_config_t object, freeing all resources.
+/* Close the given notmuch_conffile_t object, freeing all resources.
*
* Note: Any changes made to the configuration are *not* saved by this
- * function. To save changes, call notmuch_config_save before
- * notmuch_config_close.
+ * function. To save changes, call notmuch_conffile_save before
+ * notmuch_conffile_close.
*/
void
-notmuch_config_close (notmuch_config_t *config)
+notmuch_conffile_close (notmuch_conffile_t *config)
{
talloc_free (config);
}
* printing a description of the error to stderr).
*/
int
-notmuch_config_save (notmuch_config_t *config)
+notmuch_conffile_save (notmuch_conffile_t *config)
{
size_t length;
char *data, *filename;
}
/* Try not to overwrite symlinks. */
- filename = canonicalize_file_name (config->filename);
+ filename = notmuch_canonicalize_file_name (config->filename);
if (! filename) {
if (errno == ENOENT) {
filename = strdup (config->filename);
}
bool
-notmuch_config_is_new (notmuch_config_t *config)
+notmuch_conffile_is_new (notmuch_conffile_t *config)
{
return config->is_new;
}
-static const char *
-_config_get (notmuch_config_t *config, char **field,
- const char *group, const char *key)
-{
- /* read from config file and cache value, if not cached already */
- if (*field == NULL) {
- char *value;
- value = g_key_file_get_string (config->key_file, group, key, NULL);
- if (value) {
- *field = talloc_strdup (config, value);
- free (value);
- }
- }
- return *field;
-}
-
static void
-_config_set (notmuch_config_t *config, char **field,
+_config_set (notmuch_conffile_t *config,
const char *group, const char *key, const char *value)
{
g_key_file_set_string (config->key_file, group, key, value);
-
- /* drop the cached value */
- talloc_free (*field);
- *field = NULL;
-}
-
-static const char **
-_config_get_list (notmuch_config_t *config,
- const char *section, const char *key,
- const char ***outlist, size_t *list_length, size_t *ret_length)
-{
- assert (outlist);
-
- /* read from config file and cache value, if not cached already */
- if (*outlist == NULL) {
-
- char **inlist = g_key_file_get_string_list (config->key_file,
- section, key, list_length, NULL);
- if (inlist) {
- unsigned int i;
-
- *outlist = talloc_size (config, sizeof (char *) * (*list_length + 1));
-
- for (i = 0; i < *list_length; i++)
- (*outlist)[i] = talloc_strdup (*outlist, inlist[i]);
-
- (*outlist)[i] = NULL;
-
- g_strfreev (inlist);
- }
- }
-
- if (ret_length)
- *ret_length = *list_length;
-
- return *outlist;
}
static void
-_config_set_list (notmuch_config_t *config,
+_config_set_list (notmuch_conffile_t *config,
const char *group, const char *key,
const char *list[],
- size_t length, const char ***config_var )
+ size_t length)
{
g_key_file_set_string_list (config->key_file, group, key, list, length);
-
- /* drop the cached value */
- talloc_free (*config_var);
- *config_var = NULL;
-}
-
-const char *
-notmuch_config_get_database_path (notmuch_config_t *config)
-{
- char *db_path = (char *) _config_get (config, &config->database_path, "database", "path");
-
- if (db_path && *db_path != '/') {
- /* If the path in the configuration file begins with any
- * character other than /, presume that it is relative to
- * $HOME and update as appropriate.
- */
- char *abs_path = talloc_asprintf (config, "%s/%s", getenv ("HOME"), db_path);
- talloc_free (db_path);
- db_path = config->database_path = abs_path;
- }
-
- return db_path;
}
void
-notmuch_config_set_database_path (notmuch_config_t *config,
- const char *database_path)
+notmuch_conffile_set_database_path (notmuch_conffile_t *config,
+ const char *database_path)
{
- _config_set (config, &config->database_path, "database", "path", database_path);
-}
-
-const char *
-notmuch_config_get_user_name (notmuch_config_t *config)
-{
- return _config_get (config, &config->user_name, "user", "name");
+ _config_set (config, "database", "path", database_path);
}
void
-notmuch_config_set_user_name (notmuch_config_t *config,
- const char *user_name)
-{
- _config_set (config, &config->user_name, "user", "name", user_name);
-}
-
-const char *
-notmuch_config_get_user_primary_email (notmuch_config_t *config)
-{
- return _config_get (config, &config->user_primary_email, "user", "primary_email");
-}
-
-void
-notmuch_config_set_user_primary_email (notmuch_config_t *config,
- const char *primary_email)
-{
- _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
-}
-
-const char **
-notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
+notmuch_conffile_set_user_name (notmuch_conffile_t *config,
+ const char *user_name)
{
- return _config_get_list (config, "user", "other_email",
- &(config->user_other_email),
- &(config->user_other_email_length), length);
-}
-
-const char **
-notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
-{
- return _config_get_list (config, "new", "tags",
- &(config->new_tags),
- &(config->new_tags_length), length);
-}
-
-const char **
-notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
-{
- return _config_get_list (config, "new", "ignore",
- &(config->new_ignore),
- &(config->new_ignore_length), length);
+ _config_set (config, "user", "name", user_name);
}
void
-notmuch_config_set_user_other_email (notmuch_config_t *config,
- const char *list[],
- size_t length)
+notmuch_conffile_set_user_primary_email (notmuch_conffile_t *config,
+ const char *primary_email)
{
- _config_set_list (config, "user", "other_email", list, length,
- &(config->user_other_email));
+ _config_set (config, "user", "primary_email", primary_email);
}
void
-notmuch_config_set_new_tags (notmuch_config_t *config,
- const char *list[],
- size_t length)
+notmuch_conffile_set_user_other_email (notmuch_conffile_t *config,
+ const char *list[],
+ size_t length)
{
- _config_set_list (config, "new", "tags", list, length,
- &(config->new_tags));
+ _config_set_list (config, "user", "other_email", list, length);
}
void
-notmuch_config_set_new_ignore (notmuch_config_t *config,
+notmuch_conffile_set_new_tags (notmuch_conffile_t *config,
const char *list[],
size_t length)
{
- _config_set_list (config, "new", "ignore", list, length,
- &(config->new_ignore));
+ _config_set_list (config, "new", "tags", list, length);
}
-const char **
-notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
+void
+notmuch_conffile_set_new_ignore (notmuch_conffile_t *config,
+ const char *list[],
+ size_t length)
{
- return _config_get_list (config, "search", "exclude_tags",
- &(config->search_exclude_tags),
- &(config->search_exclude_tags_length), length);
+ _config_set_list (config, "new", "ignore", list, length);
}
void
-notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
- const char *list[],
- size_t length)
+notmuch_conffile_set_search_exclude_tags (notmuch_conffile_t *config,
+ const char *list[],
+ size_t length)
{
- _config_set_list (config, "search", "exclude_tags", list, length,
- &(config->search_exclude_tags));
+ _config_set_list (config, "search", "exclude_tags", list, length);
}
typedef struct config_key {
const char *name;
- bool in_db;
bool prefix;
bool (*validate)(const char *);
} config_key_info_t;
static struct config_key
config_key_table[] = {
- { "index.decrypt", true, false, NULL },
- { "index.header.", true, true, validate_field_name },
- { "query.", true, true, NULL },
+ { "index.decrypt", false, NULL },
+ { "index.header.", true, validate_field_name },
+ { "query.", true, NULL },
};
static config_key_info_t *
return NULL;
}
-static bool
-_stored_in_db (const char *item)
-{
- config_key_info_t *info;
-
- info = _config_key_info (item);
-
- return (info && info->in_db);
-}
-
static int
-_print_db_config (notmuch_config_t *config, const char *name)
+notmuch_config_command_get (notmuch_database_t *notmuch, char *item)
{
- notmuch_database_t *notmuch;
- char *val;
-
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much))
- return EXIT_FAILURE;
+ notmuch_config_values_t *list;
- /* XXX Handle UUID mismatch? */
-
- if (print_status_database ("notmuch config", notmuch,
- notmuch_database_get_config (notmuch, name, &val)))
- return EXIT_FAILURE;
-
- puts (val);
-
- return EXIT_SUCCESS;
-}
-
-static int
-notmuch_config_command_get (notmuch_config_t *config, char *item)
-{
- if (strcmp (item, "database.path") == 0) {
- printf ("%s\n", notmuch_config_get_database_path (config));
- } else if (strcmp (item, "user.name") == 0) {
- printf ("%s\n", notmuch_config_get_user_name (config));
- } else if (strcmp (item, "user.primary_email") == 0) {
- printf ("%s\n", notmuch_config_get_user_primary_email (config));
- } else if (strcmp (item, "user.other_email") == 0) {
- const char **other_email;
- size_t i, length;
-
- other_email = notmuch_config_get_user_other_email (config, &length);
- for (i = 0; i < length; i++)
- printf ("%s\n", other_email[i]);
- } else if (strcmp (item, "new.tags") == 0) {
- const char **tags;
- size_t i, length;
-
- tags = notmuch_config_get_new_tags (config, &length);
- for (i = 0; i < length; i++)
- printf ("%s\n", tags[i]);
- } else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
- printf ("%s\n",
- notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
- } else if (_stored_in_db (item)) {
- return _print_db_config (config, item);
+ if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
+ if (notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)))
+ puts ("true");
+ else
+ puts ("false");
} else {
- char **value;
- size_t i, length;
- char *group, *key;
-
- if (_item_split (item, &group, &key))
- return 1;
-
- value = g_key_file_get_string_list (config->key_file,
- group, key,
- &length, NULL);
- if (value == NULL) {
- fprintf (stderr, "Unknown configuration item: %s.%s\n",
- group, key);
- return 1;
+ for (list = notmuch_config_get_values_string (notmuch, item);
+ notmuch_config_values_valid (list);
+ notmuch_config_values_move_to_next (list)) {
+ const char *val = notmuch_config_values_get (list);
+ puts (val);
}
-
- for (i = 0; i < length; i++)
- printf ("%s\n", value[i]);
-
- g_strfreev (value);
}
-
- return 0;
+ return EXIT_SUCCESS;
}
static int
-_set_db_config (notmuch_config_t *config, const char *key, int argc, char **argv)
+_set_db_config (notmuch_database_t *notmuch, const char *key, int argc, char **argv)
{
- notmuch_database_t *notmuch;
const char *val = "";
if (argc > 1) {
val = argv[0];
}
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
+ if (print_status_database ("notmuch config", notmuch,
+ notmuch_database_reopen (notmuch,
+ NOTMUCH_DATABASE_MODE_READ_WRITE)))
return EXIT_FAILURE;
- /* XXX Handle UUID mismatch? */
-
if (print_status_database ("notmuch config", notmuch,
notmuch_database_set_config (notmuch, key, val)))
return EXIT_FAILURE;
}
static int
-notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char *argv[])
+notmuch_config_command_set (notmuch_database_t *notmuch,
+ int argc, char *argv[])
{
char *group, *key;
config_key_info_t *key_info;
+ notmuch_conffile_t *config;
+ bool update_database = false;
+ int opt_index, ret;
+ char *item;
+
+ notmuch_opt_desc_t options[] = {
+ { .opt_bool = &update_database, .name = "database" },
+ { }
+ };
+
+ opt_index = parse_arguments (argc, argv, options, 1);
+ if (opt_index < 0)
+ return EXIT_FAILURE;
+
+ argc -= opt_index;
+ argv += opt_index;
+
+ if (argc < 1) {
+ fprintf (stderr, "Error: notmuch config set requires at least "
+ "one argument.\n");
+ return EXIT_FAILURE;
+ }
+
+ item = argv[0];
+ argv++;
+ argc--;
if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
fprintf (stderr, "Error: read only option: %s\n", item);
if (key_info && key_info->validate && (! key_info->validate (item)))
return 1;
- if (key_info && key_info->in_db) {
- return _set_db_config (config, item, argc, argv);
+ if (update_database) {
+ return _set_db_config (notmuch, item, argc, argv);
}
if (_item_split (item, &group, &key))
return 1;
+ config = notmuch_conffile_open (notmuch,
+ notmuch_config_path (notmuch), false);
+ if (! config)
+ return 1;
+
/* With only the name of an item, we clear it from the
* configuration file.
*
break;
}
- return notmuch_config_save (config);
+ ret = notmuch_conffile_save (config);
+
+ notmuch_conffile_close (config);
+
+ return ret;
}
static
}
static int
-_list_db_config (notmuch_config_t *config)
-{
- notmuch_database_t *notmuch;
- notmuch_config_list_t *list;
-
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much))
- return EXIT_FAILURE;
-
- /* XXX Handle UUID mismatch? */
-
-
- if (print_status_database ("notmuch config", notmuch,
- notmuch_database_get_config_list (notmuch, "", &list)))
- return EXIT_FAILURE;
-
- for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
- printf ("%s=%s\n", notmuch_config_list_key (list), notmuch_config_list_value (list));
- }
- notmuch_config_list_destroy (list);
-
- return EXIT_SUCCESS;
-}
-
-static int
-notmuch_config_command_list (notmuch_config_t *config)
+notmuch_config_command_list (notmuch_database_t *notmuch)
{
- char **groups;
- size_t g, groups_length;
-
- groups = g_key_file_get_groups (config->key_file, &groups_length);
- if (groups == NULL)
- return 1;
-
- for (g = 0; g < groups_length; g++) {
- char **keys;
- size_t k, keys_length;
-
- keys = g_key_file_get_keys (config->key_file,
- groups[g], &keys_length, NULL);
- if (keys == NULL)
- continue;
-
- for (k = 0; k < keys_length; k++) {
- char *value;
-
- value = g_key_file_get_string (config->key_file,
- groups[g], keys[k], NULL);
- if (value != NULL) {
- printf ("%s.%s=%s\n", groups[g], keys[k], value);
- free (value);
- }
- }
-
- g_strfreev (keys);
- }
-
- g_strfreev (groups);
+ notmuch_config_pairs_t *list;
_notmuch_config_list_built_with ();
- return _list_db_config (config);
+ for (list = notmuch_config_get_pairs (notmuch, "");
+ notmuch_config_pairs_valid (list);
+ notmuch_config_pairs_move_to_next (list)) {
+ const char *value = notmuch_config_pairs_value (list);
+ if (value)
+ printf ("%s=%s\n", notmuch_config_pairs_key (list), value);
+ }
+ notmuch_config_pairs_destroy (list);
+ return EXIT_SUCCESS;
}
int
-notmuch_config_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_config_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
int ret;
int opt_index;
"one argument.\n");
return EXIT_FAILURE;
}
- ret = notmuch_config_command_get (config, argv[1]);
+ ret = notmuch_config_command_get (notmuch, argv[1]);
} else if (strcmp (argv[0], "set") == 0) {
- if (argc < 2) {
- fprintf (stderr, "Error: notmuch config set requires at least "
- "one argument.\n");
- return EXIT_FAILURE;
- }
- ret = notmuch_config_command_set (config, argv[1], argc - 2, argv + 2);
+ ret = notmuch_config_command_set (notmuch, argc, argv);
} else if (strcmp (argv[0], "list") == 0) {
- ret = notmuch_config_command_list (config);
+ ret = notmuch_config_command_list (notmuch);
} else {
fprintf (stderr, "Unrecognized argument for notmuch config: %s\n",
argv[0]);
}
-bool
-notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config)
-{
- return config->maildir_synchronize_flags;
-}
-
void
-notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
- bool synchronize_flags)
+notmuch_conffile_set_maildir_synchronize_flags (notmuch_conffile_t *config,
+ bool synchronize_flags)
{
g_key_file_set_boolean (config->key_file,
"maildir", "synchronize_flags", synchronize_flags);
- config->maildir_synchronize_flags = synchronize_flags;
}
/* return 0 on success, -1 on failure */
static int
print_count (notmuch_database_t *notmuch, const char *query_str,
- const char **exclude_tags, size_t exclude_tags_length, int output, int print_lastmod)
+ notmuch_config_values_t *exclude_tags, int output, int print_lastmod)
{
notmuch_query_t *query;
- size_t i;
int count;
unsigned int ucount;
unsigned long revision;
return -1;
}
- for (i = 0; i < exclude_tags_length; i++) {
- status = notmuch_query_add_tag_exclude (query, exclude_tags[i]);
+ for (notmuch_config_values_start (exclude_tags);
+ notmuch_config_values_valid (exclude_tags);
+ notmuch_config_values_move_to_next (exclude_tags)) {
+
+ status = notmuch_query_add_tag_exclude (query,
+ notmuch_config_values_get (exclude_tags));
if (status && status != NOTMUCH_STATUS_IGNORED) {
print_status_query ("notmuch count", query, status);
- return -1;
+ ret = -1;
+ goto DONE;
}
}
}
static int
-count_file (notmuch_database_t *notmuch, FILE *input, const char **exclude_tags,
- size_t exclude_tags_length, int output, int print_lastmod)
+count_file (notmuch_database_t *notmuch, FILE *input, notmuch_config_values_t *exclude_tags,
+ int output, int print_lastmod)
{
char *line = NULL;
ssize_t line_len;
while (! ret && (line_len = getline (&line, &line_size, input)) != -1) {
chomp_newline (line);
- ret = print_count (notmuch, line, exclude_tags, exclude_tags_length,
- output, print_lastmod);
+ ret = print_count (notmuch, line, exclude_tags, output, print_lastmod);
}
if (line)
}
int
-notmuch_count_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_count_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- notmuch_database_t *notmuch;
char *query_str;
int opt_index;
int output = OUTPUT_MESSAGES;
bool exclude = true;
- const char **search_exclude_tags = NULL;
- size_t search_exclude_tags_length = 0;
+ notmuch_config_values_t *exclude_tags = NULL;
bool batch = false;
bool print_lastmod = false;
FILE *input = stdin;
return EXIT_FAILURE;
}
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much))
- return EXIT_FAILURE;
-
notmuch_exit_if_unmatched_db_uuid (notmuch);
- query_str = query_string_from_args (config, argc - opt_index, argv + opt_index);
+ query_str = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_str == NULL) {
fprintf (stderr, "Out of memory.\n");
return EXIT_FAILURE;
}
if (exclude) {
- search_exclude_tags = notmuch_config_get_search_exclude_tags
- (config, &search_exclude_tags_length);
+ exclude_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_EXCLUDE_TAGS);
}
if (batch)
- ret = count_file (notmuch, input, search_exclude_tags,
- search_exclude_tags_length, output, print_lastmod);
+ ret = count_file (notmuch, input, exclude_tags, output, print_lastmod);
else
- ret = print_count (notmuch, query_str, search_exclude_tags,
- search_exclude_tags_length, output, print_lastmod);
+ ret = print_count (notmuch, query_str, exclude_tags, output, print_lastmod);
notmuch_database_destroy (notmuch);
output = NULL;
goto DONE;
} else
- output = NULL;
+ output = NULL;
if (output_file_name) {
ret = rename (tempname, output_file_name);
}
int
-notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_dump_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- notmuch_database_t *notmuch;
const char *query_str = NULL;
int ret;
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
- return EXIT_FAILURE;
-
notmuch_exit_if_unmatched_db_uuid (notmuch);
const char *output_file_name = NULL;
}
int
-notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_insert_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
notmuch_status_t status, close_status;
- notmuch_database_t *notmuch;
struct sigaction action;
- const char *db_path;
- const char **new_tags;
- size_t new_tags_length;
+ const char *mail_root;
+ notmuch_config_values_t *new_tags = NULL;
tag_op_list_t *tag_ops;
char *query_string = NULL;
const char *folder = "";
bool keep = false;
bool hooks = true;
bool world_readable = false;
- bool synchronize_flags;
+ notmuch_bool_t synchronize_flags;
char *maildir;
char *newpath;
int opt_index;
- unsigned int i;
+ void *local = talloc_new (NULL);
notmuch_opt_desc_t options[] = {
{ .opt_string = &folder, .name = "folder", .allow_empty = true },
notmuch_process_shared_options (argv[0]);
- db_path = notmuch_config_get_database_path (config);
- new_tags = notmuch_config_get_new_tags (config, &new_tags_length);
- synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
+ mail_root = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
+
+ new_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_NEW_TAGS);
+
+ if (print_status_database (
+ "notmuch insert",
+ notmuch,
+ notmuch_config_get_bool (notmuch, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS,
+ &synchronize_flags)))
+ return EXIT_FAILURE;
- tag_ops = tag_op_list_create (config);
+ tag_ops = tag_op_list_create (local);
if (tag_ops == NULL) {
fprintf (stderr, "Out of memory.\n");
return EXIT_FAILURE;
}
- for (i = 0; i < new_tags_length; i++) {
+ for (;
+ notmuch_config_values_valid (new_tags);
+ notmuch_config_values_move_to_next (new_tags)) {
const char *error_msg;
-
- error_msg = illegal_tag (new_tags[i], false);
+ const char *tag = notmuch_config_values_get (new_tags);
+ error_msg = illegal_tag (tag, false);
if (error_msg) {
fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
- new_tags[i], error_msg);
+ tag, error_msg);
return EXIT_FAILURE;
}
- if (tag_op_list_append (tag_ops, new_tags[i], false))
+ if (tag_op_list_append (tag_ops, tag, false))
return EXIT_FAILURE;
}
- if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
+ if (parse_tag_command_line (local, argc - opt_index, argv + opt_index,
&query_string, tag_ops))
return EXIT_FAILURE;
return EXIT_FAILURE;
}
- maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
+ maildir = talloc_asprintf (local, "%s/%s", mail_root, folder);
if (! maildir) {
fprintf (stderr, "Out of memory\n");
return EXIT_FAILURE;
}
strip_trailing (maildir, '/');
- if (create_folder && ! maildir_create_folder (config, maildir, world_readable))
+ if (create_folder && ! maildir_create_folder (local, maildir, world_readable))
return EXIT_FAILURE;
/* Set up our handler for SIGINT. We do not set SA_RESTART so that copying
sigaction (SIGINT, &action, NULL);
/* Write the message to the Maildir new directory. */
- newpath = maildir_write_new (config, STDIN_FILENO, maildir, world_readable);
+ newpath = maildir_write_new (local, STDIN_FILENO, maildir, world_readable);
if (! newpath) {
return EXIT_FAILURE;
}
- status = notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much);
- if (status)
- return keep ? NOTMUCH_STATUS_SUCCESS : status_to_exit (status);
-
notmuch_exit_if_unmatched_db_uuid (notmuch);
status = notmuch_process_shared_indexing_options (notmuch);
status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexing_cli_choices.opts);
/* Commit changes. */
- close_status = notmuch_database_destroy (notmuch);
+ close_status = notmuch_database_close (notmuch);
if (close_status) {
/* Hold on to the first error, if any. */
if (! status)
if (hooks && status == NOTMUCH_STATUS_SUCCESS) {
/* Ignore hook failures. */
- notmuch_run_hook (db_path, "post-insert");
+ notmuch_run_hook (notmuch, "post-insert");
}
+ notmuch_database_destroy (notmuch);
+
+ talloc_free (local);
+
return status_to_exit (status);
}
typedef struct {
const char *db_path;
+ const char *mail_root;
int output_is_a_tty;
enum verbosity verbosity;
bool debug;
bool full_scan;
- const char **new_tags;
- size_t new_tags_length;
+ notmuch_config_values_t *new_tags;
const char **ignore_verbatim;
size_t ignore_verbatim_length;
regex_t *ignore_regex;
_filename_list_t *removed_directories;
_filename_list_t *directory_mtimes;
- bool synchronize_flags;
+ notmuch_bool_t synchronize_flags;
} add_files_state_t;
static volatile sig_atomic_t do_print_progress = 0;
}
static bool
-_setup_ignore (notmuch_config_t *config, add_files_state_t *state)
+_setup_ignore (notmuch_database_t *notmuch, add_files_state_t *state)
{
- const char **ignore_list, **ignore;
+ notmuch_config_values_t *ignore_list;
int nregex = 0, nverbatim = 0;
const char **verbatim = NULL;
regex_t *regex = NULL;
- ignore_list = notmuch_config_get_new_ignore (config, NULL);
- if (! ignore_list)
- return true;
-
- for (ignore = ignore_list; *ignore; ignore++) {
- const char *s = *ignore;
+ for (ignore_list = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_NEW_IGNORE);
+ notmuch_config_values_valid (ignore_list);
+ notmuch_config_values_move_to_next (ignore_list)) {
+ const char *s = notmuch_config_values_get (ignore_list);
size_t len = strlen (s);
if (len == 0) {
return false;
}
- r = talloc_strndup (config, s + 1, len - 2);
- regex = talloc_realloc (config, regex, regex_t, nregex + 1);
+ r = talloc_strndup (notmuch, s + 1, len - 2);
+ regex = talloc_realloc (notmuch, regex, regex_t, nregex + 1);
preg = ®ex[nregex];
rerr = regcomp (preg, r, REG_EXTENDED | REG_NOSUB);
talloc_free (r);
} else {
- verbatim = talloc_realloc (config, verbatim, const char *,
+ verbatim = talloc_realloc (notmuch, verbatim, const char *,
nverbatim + 1);
verbatim[nverbatim++] = s;
}
}
static char *
-_get_relative_path (const char *db_path, const char *dirpath, const char *entry)
+_get_relative_path (const char *mail_root, const char *dirpath, const char *entry)
{
- size_t db_path_len = strlen (db_path);
+ size_t mail_root_len = strlen (mail_root);
/* paranoia? */
- if (strncmp (dirpath, db_path, db_path_len) != 0) {
+ if (strncmp (dirpath, mail_root, mail_root_len) != 0) {
fprintf (stderr, "Warning: '%s' is not a subdirectory of '%s'\n",
- dirpath, db_path);
+ dirpath, mail_root);
return NULL;
}
- dirpath += db_path_len;
+ dirpath += mail_root_len;
while (*dirpath == '/')
dirpath++;
if (state->ignore_regex_length == 0)
return false;
- path = _get_relative_path (state->db_path, dirpath, entry);
+ path = _get_relative_path (state->mail_root, dirpath, entry);
if (! path)
return false;
add_files_state_t *state)
{
notmuch_message_t *message = NULL;
- const char **tag;
+ const char *tag;
notmuch_status_t status;
status = notmuch_database_begin_atomic (notmuch);
if (state->synchronize_flags)
notmuch_message_maildir_flags_to_tags (message);
- for (tag = state->new_tags; *tag != NULL; tag++) {
+ for (notmuch_config_values_start (state->new_tags);
+ notmuch_config_values_valid (state->new_tags);
+ notmuch_config_values_move_to_next (state->new_tags)) {
notmuch_bool_t is_set;
+
+ tag = notmuch_config_values_get (state->new_tags);
/* Currently all errors from has_maildir_flag are fatal */
if ((status = notmuch_message_has_maildir_flag_st (message, 'S', &is_set)))
goto DONE;
- if (strcmp ("unread", *tag) != 0 || ! is_set) {
- notmuch_message_add_tag (message, *tag);
+ if (strcmp ("unread", tag) != 0 || ! is_set) {
+ notmuch_message_add_tag (message, tag);
}
}
char *absolute = talloc_asprintf (state->removed_directories,
"%s/%s", path, filename);
if (state->debug)
- printf ("(D) add_files, pass 2: queuing passed directory %s for deletion from database\n",
- absolute);
+ printf (
+ "(D) add_files, pass 2: queuing passed directory %s for deletion from database\n",
+ absolute);
_filename_list_add (state->removed_directories, absolute);
}
notmuch_filenames_get (db_subdirs));
if (state->debug)
- printf ("(D) add_files, pass 3: queuing leftover directory %s for deletion from database\n",
- absolute);
+ printf (
+ "(D) add_files, pass 3: queuing leftover directory %s for deletion from database\n",
+ absolute);
_filename_list_add (state->removed_directories, absolute);
/* Recursively remove all filenames from the database referring to
* 'path' (or to any of its children). */
static notmuch_status_t
-_remove_directory (void *ctx,
- notmuch_database_t *notmuch,
+_remove_directory (notmuch_database_t *notmuch,
const char *path,
add_files_state_t *add_files_state)
{
for (files = notmuch_directory_get_child_files (directory);
notmuch_filenames_valid (files);
notmuch_filenames_move_to_next (files)) {
- absolute = talloc_asprintf (ctx, "%s/%s", path,
+ absolute = talloc_asprintf (notmuch, "%s/%s", path,
notmuch_filenames_get (files));
status = remove_filename (notmuch, absolute, add_files_state);
talloc_free (absolute);
for (subdirs = notmuch_directory_get_child_directories (directory);
notmuch_filenames_valid (subdirs);
notmuch_filenames_move_to_next (subdirs)) {
- absolute = talloc_asprintf (ctx, "%s/%s", path,
+ absolute = talloc_asprintf (notmuch, "%s/%s", path,
notmuch_filenames_get (subdirs));
- status = _remove_directory (ctx, notmuch, absolute, add_files_state);
+ status = _remove_directory (notmuch, absolute, add_files_state);
talloc_free (absolute);
if (status)
goto DONE;
printf ("\n");
}
+static int
+_maybe_upgrade (notmuch_database_t *notmuch, add_files_state_t *state)
+{
+ if (notmuch_database_needs_upgrade (notmuch)) {
+ time_t now = time (NULL);
+ struct tm *gm_time = gmtime (&now);
+ int err;
+ notmuch_status_t status;
+ const char *backup_dir = notmuch_config_get (notmuch, NOTMUCH_CONFIG_BACKUP_DIR);
+ const char *backup_name;
+
+ err = mkdir (backup_dir, 0755);
+ if (err && errno != EEXIST) {
+ fprintf (stderr, "Failed to create %s: %s\n", backup_dir, strerror (errno));
+ return EXIT_FAILURE;
+ }
+
+ /* since dump files are written atomically, the amount of
+ * harm from overwriting one within a second seems
+ * relatively small. */
+ backup_name = talloc_asprintf (notmuch, "%s/dump-%04d%02d%02dT%02d%02d%02d.gz",
+ backup_dir,
+ gm_time->tm_year + 1900,
+ gm_time->tm_mon + 1,
+ gm_time->tm_mday,
+ gm_time->tm_hour,
+ gm_time->tm_min,
+ gm_time->tm_sec);
+
+ if (state->verbosity >= VERBOSITY_NORMAL) {
+ printf ("Welcome to a new version of notmuch! Your database will now be upgraded.\n");
+ printf ("This process is safe to interrupt.\n");
+ printf ("Backing up tags to %s...\n", backup_name);
+ }
+
+ if (notmuch_database_dump (notmuch, backup_name, "",
+ DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, true)) {
+ fprintf (stderr, "Backup failed. Aborting upgrade.");
+ return EXIT_FAILURE;
+ }
+
+ gettimeofday (&state->tv_start, NULL);
+ status = notmuch_database_upgrade (
+ notmuch,
+ state->verbosity >= VERBOSITY_NORMAL ? upgrade_print_progress : NULL,
+ state);
+ if (status) {
+ printf ("Upgrade failed: %s\n",
+ notmuch_status_to_string (status));
+ notmuch_database_destroy (notmuch);
+ return EXIT_FAILURE;
+ }
+ if (state->verbosity >= VERBOSITY_NORMAL)
+ printf ("Your notmuch database has now been upgraded.\n");
+ }
+ return EXIT_SUCCESS;
+}
+
int
-notmuch_new_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_new_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- notmuch_database_t *notmuch;
add_files_state_t add_files_state = {
.verbosity = VERBOSITY_NORMAL,
.debug = false,
};
struct timeval tv_start;
int ret = 0;
- struct stat st;
- const char *db_path;
- char *dot_notmuch_path;
+ const char *db_path, *mail_root;
struct sigaction action;
_filename_node_t *f;
int opt_index;
else if (verbose)
add_files_state.verbosity = VERBOSITY_VERBOSE;
- add_files_state.new_tags = notmuch_config_get_new_tags (config, &add_files_state.new_tags_length);
- add_files_state.synchronize_flags = notmuch_config_get_maildir_synchronize_flags (config);
- db_path = notmuch_config_get_database_path (config);
+ add_files_state.new_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_NEW_TAGS);
+
+ if (print_status_database (
+ "notmuch new",
+ notmuch,
+ notmuch_config_get_bool (notmuch, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS,
+ &add_files_state.synchronize_flags)))
+ return EXIT_FAILURE;
+
+ db_path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH);
add_files_state.db_path = db_path;
- if (! _setup_ignore (config, &add_files_state))
+ mail_root = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
+ add_files_state.mail_root = mail_root;
+
+ if (! _setup_ignore (notmuch, &add_files_state))
return EXIT_FAILURE;
- for (i = 0; i < add_files_state.new_tags_length; i++) {
- const char *error_msg;
+ for (notmuch_config_values_start (add_files_state.new_tags);
+ notmuch_config_values_valid (add_files_state.new_tags);
+ notmuch_config_values_move_to_next (add_files_state.new_tags)) {
+ const char *tag, *error_msg;
- error_msg = illegal_tag (add_files_state.new_tags[i], false);
+ tag = notmuch_config_values_get (add_files_state.new_tags);
+ error_msg = illegal_tag (tag, false);
if (error_msg) {
- fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
- add_files_state.new_tags[i], error_msg);
+ fprintf (stderr, "Error: tag '%s' in new.tags: %s\n", tag, error_msg);
return EXIT_FAILURE;
}
}
if (hooks) {
- ret = notmuch_run_hook (db_path, "pre-new");
+ /* Drop write lock to run hook */
+ status = notmuch_database_reopen (notmuch, NOTMUCH_DATABASE_MODE_READ_ONLY);
+ if (print_status_database ("notmuch new", notmuch, status))
+ return EXIT_FAILURE;
+
+ ret = notmuch_run_hook (notmuch, "pre-new");
if (ret)
return EXIT_FAILURE;
- }
- dot_notmuch_path = talloc_asprintf (config, "%s/%s", db_path, ".notmuch");
+ /* acquire write lock again */
+ status = notmuch_database_reopen (notmuch, NOTMUCH_DATABASE_MODE_READ_WRITE);
+ if (print_status_database ("notmuch new", notmuch, status))
+ return EXIT_FAILURE;
+ }
- if (stat (dot_notmuch_path, &st)) {
- int count;
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
- count = 0;
- count_files (db_path, &count, &add_files_state);
+ if (notmuch_database_get_revision (notmuch, NULL) == 0) {
+ int count = 0;
+ count_files (mail_root, &count, &add_files_state);
if (interrupted)
return EXIT_FAILURE;
if (add_files_state.verbosity >= VERBOSITY_NORMAL)
printf ("Found %d total files (that's not much mail).\n", count);
- if (notmuch_database_create (db_path, ¬much))
- return EXIT_FAILURE;
+
add_files_state.total_files = count;
} else {
- char *status_string = NULL;
- if (notmuch_database_open_verbose (db_path, NOTMUCH_DATABASE_MODE_READ_WRITE,
- ¬much, &status_string)) {
- if (status_string) {
- fputs (status_string, stderr);
- free (status_string);
- }
+ if (_maybe_upgrade (notmuch, &add_files_state))
return EXIT_FAILURE;
- }
-
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
- if (notmuch_database_needs_upgrade (notmuch)) {
- time_t now = time (NULL);
- struct tm *gm_time = gmtime (&now);
-
- /* since dump files are written atomically, the amount of
- * harm from overwriting one within a second seems
- * relatively small. */
-
- const char *backup_name =
- talloc_asprintf (notmuch, "%s/dump-%04d%02d%02dT%02d%02d%02d.gz",
- dot_notmuch_path,
- gm_time->tm_year + 1900,
- gm_time->tm_mon + 1,
- gm_time->tm_mday,
- gm_time->tm_hour,
- gm_time->tm_min,
- gm_time->tm_sec);
-
- if (add_files_state.verbosity >= VERBOSITY_NORMAL) {
- printf ("Welcome to a new version of notmuch! Your database will now be upgraded.\n");
- printf ("This process is safe to interrupt.\n");
- printf ("Backing up tags to %s...\n", backup_name);
- }
-
- if (notmuch_database_dump (notmuch, backup_name, "",
- DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, true)) {
- fprintf (stderr, "Backup failed. Aborting upgrade.");
- return EXIT_FAILURE;
- }
-
- gettimeofday (&add_files_state.tv_start, NULL);
- status = notmuch_database_upgrade (
- notmuch,
- add_files_state.verbosity >= VERBOSITY_NORMAL ? upgrade_print_progress : NULL,
- &add_files_state);
- if (status) {
- printf ("Upgrade failed: %s\n",
- notmuch_status_to_string (status));
- notmuch_database_destroy (notmuch);
- return EXIT_FAILURE;
- }
- if (add_files_state.verbosity >= VERBOSITY_NORMAL)
- printf ("Your notmuch database has now been upgraded.\n");
- }
add_files_state.total_files = 0;
}
action.sa_flags = SA_RESTART;
sigaction (SIGINT, &action, NULL);
- talloc_free (dot_notmuch_path);
- dot_notmuch_path = NULL;
-
gettimeofday (&add_files_state.tv_start, NULL);
- add_files_state.removed_files = _filename_list_create (config);
- add_files_state.removed_directories = _filename_list_create (config);
- add_files_state.directory_mtimes = _filename_list_create (config);
+ add_files_state.removed_files = _filename_list_create (notmuch);
+ add_files_state.removed_directories = _filename_list_create (notmuch);
+ add_files_state.directory_mtimes = _filename_list_create (notmuch);
if (add_files_state.verbosity == VERBOSITY_NORMAL &&
add_files_state.output_is_a_tty && ! debugger_is_active ()) {
timer_is_active = true;
}
- ret = add_files (notmuch, db_path, &add_files_state);
+ ret = add_files (notmuch, mail_root, &add_files_state);
if (ret)
goto DONE;
if (do_print_progress) {
do_print_progress = 0;
generic_print_progress ("Cleaned up", "messages",
- tv_start, add_files_state.removed_messages + add_files_state.renamed_messages,
+ tv_start, add_files_state.removed_messages +
+ add_files_state.renamed_messages,
add_files_state.removed_files->count);
}
}
gettimeofday (&tv_start, NULL);
for (f = add_files_state.removed_directories->head, i = 0; f && ! interrupted; f = f->next, i++) {
- ret = _remove_directory (config, notmuch, f->filename, &add_files_state);
+ ret = _remove_directory (notmuch, f->filename, &add_files_state);
if (ret)
goto DONE;
if (do_print_progress) {
fprintf (stderr, "Note: A fatal error was encountered: %s\n",
notmuch_status_to_string (ret));
- notmuch_database_destroy (notmuch);
+ notmuch_database_close (notmuch);
if (hooks && ! ret && ! interrupted)
- ret = notmuch_run_hook (db_path, "post-new");
+ ret = notmuch_run_hook (notmuch, "post-new");
+
+ notmuch_database_destroy (notmuch);
if (ret || interrupted)
return EXIT_FAILURE;
}
int
-notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_reindex_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
char *query_string = NULL;
- notmuch_database_t *notmuch;
struct sigaction action;
int opt_index;
int ret;
notmuch_process_shared_options (argv[0]);
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
- return EXIT_FAILURE;
-
notmuch_exit_if_unmatched_db_uuid (notmuch);
status = notmuch_process_shared_indexing_options (notmuch);
return EXIT_FAILURE;
}
- query_string = query_string_from_args (config, argc - opt_index, argv + opt_index);
+ query_string = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_string == NULL) {
fprintf (stderr, "Out of memory\n");
return EXIT_FAILURE;
/* Match given string against user's configured "primary" and "other"
* addresses according to mode. */
static const char *
-address_match (const char *str, notmuch_config_t *config, address_match_t mode)
+address_match (const char *str, notmuch_database_t *notmuch, address_match_t mode)
{
const char *primary;
- const char **other;
- size_t i, other_len;
+ notmuch_config_values_t *other = NULL;
if (! str || *str == '\0')
return NULL;
- primary = notmuch_config_get_user_primary_email (config);
+ primary = notmuch_config_get (notmuch, NOTMUCH_CONFIG_PRIMARY_EMAIL);
if (match_address (str, primary, mode))
return primary;
- other = notmuch_config_get_user_other_email (config, &other_len);
- for (i = 0; i < other_len; i++) {
- if (match_address (str, other[i], mode))
- return other[i];
- }
+ for (other = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_OTHER_EMAIL);
+ notmuch_config_values_valid (other);
+ notmuch_config_values_move_to_next (other)) {
+ const char *addr = notmuch_config_values_get (other);
+ if (match_address (str, addr, mode))
+ return addr;
+ }
return NULL;
}
* user's "primary" or "other" addresses. If so, return the matching
* address, NULL otherwise. */
static const char *
-user_address_in_string (const char *str, notmuch_config_t *config)
+user_address_in_string (const char *str, notmuch_database_t *notmuch)
{
- return address_match (str, config, USER_ADDRESS_IN_STRING);
+ return address_match (str, notmuch, USER_ADDRESS_IN_STRING);
}
/* Do any of the addresses configured as one of the user's "primary"
* or "other" addresses contain the given string. If so, return the
* matching address, NULL otherwise. */
static const char *
-string_in_user_address (const char *str, notmuch_config_t *config)
+string_in_user_address (const char *str, notmuch_database_t *notmuch)
{
- return address_match (str, config, STRING_IN_USER_ADDRESS);
+ return address_match (str, notmuch, STRING_IN_USER_ADDRESS);
}
/* Is the given address configured as one of the user's "primary" or
* "other" addresses. */
static bool
-address_is_users (const char *address, notmuch_config_t *config)
+address_is_users (const char *address, notmuch_database_t *notmuch)
{
- return address_match (address, config, STRING_IS_USER_ADDRESS) != NULL;
+ return address_match (address, notmuch, STRING_IS_USER_ADDRESS) != NULL;
}
/* Scan addresses in 'list'.
*/
static unsigned int
scan_address_list (InternetAddressList *list,
- notmuch_config_t *config,
+ notmuch_database_t *notmuch,
GMimeMessage *message,
GMimeAddressType type,
const char **user_from)
group = INTERNET_ADDRESS_GROUP (address);
group_list = internet_address_group_get_members (group);
- n += scan_address_list (group_list, config, message, type, user_from);
+ n += scan_address_list (group_list, notmuch, message, type, user_from);
} else {
InternetAddressMailbox *mailbox;
const char *name;
name = internet_address_get_name (address);
addr = internet_address_mailbox_get_addr (mailbox);
- if (address_is_users (addr, config)) {
+ if (address_is_users (addr, notmuch)) {
if (user_from && *user_from == NULL)
*user_from = addr;
} else if (message) {
*/
static const char *
add_recipients_from_message (GMimeMessage *reply,
- notmuch_config_t *config,
+ notmuch_database_t *notmuch,
GMimeMessage *message,
bool reply_all)
{
recipients = reply_to_map[i].get_header (message);
- n += scan_address_list (recipients, config, reply,
+ n += scan_address_list (recipients, notmuch, reply,
reply_to_map[i].recipient_type, &from_addr);
if (! reply_all && n) {
* Return the address that was found, if any, and NULL otherwise.
*/
static const char *
-guess_from_in_received_for (notmuch_config_t *config, const char *received)
+guess_from_in_received_for (notmuch_database_t *notmuch, const char *received)
{
const char *ptr;
if (! ptr)
return NULL;
- return user_address_in_string (ptr, config);
+ return user_address_in_string (ptr, notmuch);
}
/*
* Return the address that was found, if any, and NULL otherwise.
*/
static const char *
-guess_from_in_received_by (notmuch_config_t *config, const char *received)
+guess_from_in_received_by (notmuch_database_t *notmuch, const char *received)
{
const char *addr;
const char *by = received;
*/
*(tld - 1) = '.';
- addr = string_in_user_address (domain, config);
+ addr = string_in_user_address (domain, notmuch);
if (addr) {
free (mta);
return addr;
* Return the address that was found, if any, and NULL otherwise.
*/
static const char *
-guess_from_in_received_headers (notmuch_config_t *config,
- notmuch_message_t *message)
+guess_from_in_received_headers (notmuch_message_t *message)
{
const char *received, *addr;
char *sanitized;
+ notmuch_database_t *notmuch = notmuch_message_get_database (message);
+
received = notmuch_message_get_header (message, "received");
if (! received)
return NULL;
if (! sanitized)
return NULL;
- addr = guess_from_in_received_for (config, sanitized);
+ addr = guess_from_in_received_for (notmuch, sanitized);
if (! addr)
- addr = guess_from_in_received_by (config, sanitized);
+ addr = guess_from_in_received_by (notmuch, sanitized);
talloc_free (sanitized);
* Return the address that was found, if any, and NULL otherwise.
*/
static const char *
-get_from_in_to_headers (notmuch_config_t *config, notmuch_message_t *message)
+get_from_in_to_headers (notmuch_message_t *message)
{
size_t i;
const char *tohdr, *addr;
"Delivered-To",
};
+ notmuch_database_t *notmuch = notmuch_message_get_database (message);
+
for (i = 0; i < ARRAY_SIZE (to_headers); i++) {
tohdr = notmuch_message_get_header (message, to_headers[i]);
/* Note: tohdr potentially contains a list of email addresses. */
- addr = user_address_in_string (tohdr, config);
+ addr = user_address_in_string (tohdr, notmuch);
if (addr)
return addr;
}
static GMimeMessage *
create_reply_message (void *ctx,
- notmuch_config_t *config,
notmuch_message_t *message,
GMimeMessage *mime_message,
bool reply_all,
{
const char *subject, *from_addr = NULL;
const char *in_reply_to, *orig_references, *references;
-
+ notmuch_database_t *notmuch = notmuch_message_get_database (message);
/*
* Use the below header order for limited headers, "pretty" order
* otherwise.
g_mime_object_set_header (GMIME_OBJECT (reply), "References", references, NULL);
- from_addr = add_recipients_from_message (reply, config,
+ from_addr = add_recipients_from_message (reply, notmuch,
mime_message, reply_all);
/* The above is all that is needed for limited headers. */
* Delivered-To: headers.
*/
if (from_addr == NULL)
- from_addr = get_from_in_to_headers (config, message);
+ from_addr = get_from_in_to_headers (message);
/*
* Check for a (for <email@add.res>) clause in Received: headers,
* of Received: headers
*/
if (from_addr == NULL)
- from_addr = guess_from_in_received_headers (config, message);
+ from_addr = guess_from_in_received_headers (message);
/* Default to user's primary address. */
if (from_addr == NULL)
- from_addr = notmuch_config_get_user_primary_email (config);
+ from_addr = notmuch_config_get (notmuch, NOTMUCH_CONFIG_PRIMARY_EMAIL);
from_addr = talloc_asprintf (ctx, "%s <%s>",
- notmuch_config_get_user_name (config),
+ notmuch_config_get (notmuch, NOTMUCH_CONFIG_USER_NAME),
from_addr);
g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr, NULL);
};
static int
-do_reply (notmuch_config_t *config,
+do_reply (notmuch_database_t *notmuch,
notmuch_query_t *query,
notmuch_show_params_t *params,
int format,
return 1;
if (count != 1) {
- fprintf (stderr, "Error: search term did not match precisely one message (matched %u messages).\n", count);
+ fprintf (stderr,
+ "Error: search term did not match precisely one message (matched %u messages).\n",
+ count);
return 1;
}
if (format == FORMAT_JSON)
- sp = sprinter_json_create (config, stdout);
+ sp = sprinter_json_create (notmuch, stdout);
else
- sp = sprinter_sexp_create (config, stdout);
+ sp = sprinter_sexp_create (notmuch, stdout);
}
status = notmuch_query_search_messages (query, &messages);
notmuch_messages_move_to_next (messages)) {
message = notmuch_messages_get (messages);
- if (mime_node_open (config, message, ¶ms->crypto, &node))
+ if (mime_node_open (notmuch, message, ¶ms->crypto, &node))
return 1;
- reply = create_reply_message (config, config, message,
+ reply = create_reply_message (notmuch, message,
GMIME_MESSAGE (node->part), reply_all,
format == FORMAT_HEADERS_ONLY);
if (! reply)
/* Start the original */
sp->map_key (sp, "original");
- format_part_sprinter (config, sp, node, true, false);
+ format_part_sprinter (notmuch, sp, node, true, false);
/* End */
sp->end (sp);
}
int
-notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_reply_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- notmuch_database_t *notmuch;
notmuch_query_t *query;
char *query_string;
int opt_index;
notmuch_exit_if_unsupported_format ();
- query_string = query_string_from_args (config, argc - opt_index, argv + opt_index);
+ query_string = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_string == NULL) {
fprintf (stderr, "Out of memory\n");
return EXIT_FAILURE;
return EXIT_FAILURE;
}
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_ONLY, ¬much))
- return EXIT_FAILURE;
-
notmuch_exit_if_unmatched_db_uuid (notmuch);
query = notmuch_query_create (notmuch, query_string);
return EXIT_FAILURE;
}
- if (do_reply (config, query, ¶ms, format, reply_all) != 0)
+ if (do_reply (notmuch, query, ¶ms, format, reply_all) != 0)
return EXIT_FAILURE;
_notmuch_crypto_cleanup (¶ms.crypto);
}
int
-notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_restore_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- notmuch_database_t *notmuch;
bool accumulate = false;
tag_op_flag_t flags = 0;
tag_op_list_t *tag_ops;
int include = 0;
int input_format = DUMP_FORMAT_AUTO;
int errnum;
+ notmuch_bool_t synchronize_flags;
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
+ if (print_status_database (
+ "notmuch restore",
+ notmuch,
+ notmuch_config_get_bool (notmuch, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS,
+ &synchronize_flags)))
return EXIT_FAILURE;
- if (notmuch_config_get_maildir_synchronize_flags (config))
+ if (synchronize_flags)
flags |= TAG_FLAG_MAILDIR_SYNC;
notmuch_opt_desc_t options[] = {
goto DONE;
}
- tag_ops = tag_op_list_create (config);
+ tag_ops = tag_op_list_create (notmuch);
if (tag_ops == NULL) {
fprintf (stderr, "Out of memory.\n");
ret = EXIT_FAILURE;
if (ret)
goto DONE;
}
- if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] == '=') {
+ if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] ==
+ '=') {
ret = process_properties_line (notmuch, line + 2);
if (ret)
goto DONE;
}
char *p;
+
for (p = line; (input_format == DUMP_FORMAT_AUTO) && *p; p++) {
if (*p == '(')
input_format = DUMP_FORMAT_SUP;
if (line_ctx != NULL)
talloc_free (line_ctx);
- line_ctx = talloc_new (config);
+ line_ctx = talloc_new (notmuch);
- if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] == '=') {
+ if ((include & DUMP_INCLUDE_PROPERTIES) && line_len >= 2 && line[0] == '#' && line[1] ==
+ '=') {
ret = process_properties_line (notmuch, line + 2);
if (ret)
goto DONE;
typedef struct {
notmuch_database_t *notmuch;
+ void *talloc_ctx;
int format_sel;
sprinter_t *format;
int exclude;
}
static int
-_notmuch_search_prepare (search_context_t *ctx, notmuch_config_t *config, int argc, char *argv[])
+_notmuch_search_prepare (search_context_t *ctx, int argc, char *argv[])
{
char *query_str;
- unsigned int i;
- char *status_string = NULL;
+
+ if (! ctx->talloc_ctx)
+ ctx->talloc_ctx = talloc_new (NULL);
switch (ctx->format_sel) {
case NOTMUCH_FORMAT_TEXT:
- ctx->format = sprinter_text_create (config, stdout);
+ ctx->format = sprinter_text_create (ctx->talloc_ctx, stdout);
break;
case NOTMUCH_FORMAT_TEXT0:
if (ctx->output == OUTPUT_SUMMARY) {
fprintf (stderr, "Error: --format=text0 is not compatible with --output=summary.\n");
return EXIT_FAILURE;
}
- ctx->format = sprinter_text0_create (config, stdout);
+ ctx->format = sprinter_text0_create (ctx->talloc_ctx, stdout);
break;
case NOTMUCH_FORMAT_JSON:
- ctx->format = sprinter_json_create (config, stdout);
+ ctx->format = sprinter_json_create (ctx->talloc_ctx, stdout);
break;
case NOTMUCH_FORMAT_SEXP:
- ctx->format = sprinter_sexp_create (config, stdout);
+ ctx->format = sprinter_sexp_create (ctx->talloc_ctx, stdout);
break;
default:
/* this should never happen */
notmuch_exit_if_unsupported_format ();
- if (notmuch_database_open_verbose (
- notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_ONLY, &ctx->notmuch, &status_string)) {
-
- if (status_string) {
- fputs (status_string, stderr);
- free (status_string);
- }
-
- return EXIT_FAILURE;
- }
-
notmuch_exit_if_unmatched_db_uuid (ctx->notmuch);
query_str = query_string_from_args (ctx->notmuch, argc, argv);
}
if (ctx->exclude != NOTMUCH_EXCLUDE_FALSE) {
- const char **search_exclude_tags;
- size_t search_exclude_tags_length;
+ notmuch_config_values_t *exclude_tags;
notmuch_status_t status;
- search_exclude_tags = notmuch_config_get_search_exclude_tags (
- config, &search_exclude_tags_length);
+ for (exclude_tags = notmuch_config_get_values (ctx->notmuch, NOTMUCH_CONFIG_EXCLUDE_TAGS);
+ notmuch_config_values_valid (exclude_tags);
+ notmuch_config_values_move_to_next (exclude_tags)) {
- for (i = 0; i < search_exclude_tags_length; i++) {
- status = notmuch_query_add_tag_exclude (ctx->query, search_exclude_tags[i]);
+ status = notmuch_query_add_tag_exclude (ctx->query,
+ notmuch_config_values_get (exclude_tags));
if (status && status != NOTMUCH_STATUS_IGNORED) {
print_status_query ("notmuch search", ctx->query, status);
return EXIT_FAILURE;
}
}
-
notmuch_query_set_omit_excluded (ctx->query, ctx->exclude);
}
};
int
-notmuch_search_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_search_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
search_context_t *ctx = &search_context;
int opt_index, ret;
{ }
};
+ ctx->notmuch = notmuch;
ctx->output = OUTPUT_SUMMARY;
opt_index = parse_arguments (argc, argv, options, 1);
if (opt_index < 0)
if (ctx->output != OUTPUT_FILES && ctx->output != OUTPUT_MESSAGES &&
ctx->dupe != -1) {
- fprintf (stderr, "Error: --duplicate=N is only supported with --output=files and --output=messages.\n");
+ fprintf (stderr,
+ "Error: --duplicate=N is only supported with --output=files and --output=messages.\n");
return EXIT_FAILURE;
}
- if (_notmuch_search_prepare (ctx, config,
- argc - opt_index, argv + opt_index))
+ if (_notmuch_search_prepare (ctx, argc - opt_index, argv + opt_index))
return EXIT_FAILURE;
switch (ctx->output) {
}
int
-notmuch_address_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_address_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
search_context_t *ctx = &search_context;
int opt_index, ret;
{ }
};
+ ctx->notmuch = notmuch;
+
opt_index = parse_arguments (argc, argv, options, 1);
if (opt_index < 0)
return EXIT_FAILURE;
return EXIT_FAILURE;
}
- if (_notmuch_search_prepare (ctx, config,
- argc - opt_index, argv + opt_index))
+ if (_notmuch_search_prepare (ctx, argc - opt_index, argv + opt_index))
return EXIT_FAILURE;
ctx->addresses = g_hash_table_new_full (strcase_hash, strcase_equal,
}
static void
-print_tag_list (const char **tags, size_t tags_len)
+print_tag_list (notmuch_config_values_t *tags)
{
- unsigned int i;
+ bool first = false;
- for (i = 0; i < tags_len; i++) {
- if (i != 0)
+ for (;
+ notmuch_config_values_valid (tags);
+ notmuch_config_values_move_to_next (tags)) {
+ if (! first)
printf (" ");
- printf ("%s", tags[i]);
+ first = false;
+ printf ("%s", notmuch_config_values_get (tags));
}
}
}
int
-notmuch_setup_command (notmuch_config_t *config,
+notmuch_setup_command (notmuch_database_t *notmuch,
int argc, char *argv[])
{
char *response = NULL;
size_t response_size = 0;
- const char **old_other_emails;
- size_t old_other_emails_len;
GPtrArray *other_emails;
- unsigned int i;
- const char **new_tags;
- size_t new_tags_len;
- const char **search_exclude_tags;
- size_t search_exclude_tags_len;
+ notmuch_config_values_t *new_tags, *search_exclude_tags, *emails;
+ notmuch_conffile_t *config;
#define prompt(format, ...) \
do { \
fprintf (stderr, "Warning: ignoring --uuid=%s\n",
notmuch_requested_db_uuid);
- if (notmuch_config_is_new (config))
+ config = notmuch_conffile_open (notmuch,
+ notmuch_config_path (notmuch), true);
+ if (! config)
+ return EXIT_FAILURE;
+
+ if (notmuch_conffile_is_new (config))
welcome_message_pre_setup ();
- prompt ("Your full name [%s]: ", notmuch_config_get_user_name (config));
+ prompt ("Your full name [%s]: ", notmuch_config_get (notmuch, NOTMUCH_CONFIG_USER_NAME));
if (strlen (response))
- notmuch_config_set_user_name (config, response);
+ notmuch_conffile_set_user_name (config, response);
prompt ("Your primary email address [%s]: ",
- notmuch_config_get_user_primary_email (config));
+ notmuch_config_get (notmuch, NOTMUCH_CONFIG_PRIMARY_EMAIL));
if (strlen (response))
- notmuch_config_set_user_primary_email (config, response);
+ notmuch_conffile_set_user_primary_email (config, response);
other_emails = g_ptr_array_new ();
- old_other_emails = notmuch_config_get_user_other_email (config,
- &old_other_emails_len);
- for (i = 0; i < old_other_emails_len; i++) {
- prompt ("Additional email address [%s]: ", old_other_emails[i]);
+ for (emails = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_OTHER_EMAIL);
+ notmuch_config_values_valid (emails);
+ notmuch_config_values_move_to_next (emails)) {
+ const char *email = notmuch_config_values_get (emails);
+
+ prompt ("Additional email address [%s]: ", email);
if (strlen (response))
g_ptr_array_add (other_emails, talloc_strdup (config, response));
else
- g_ptr_array_add (other_emails, talloc_strdup (config,
- old_other_emails[i]));
+ g_ptr_array_add (other_emails, talloc_strdup (config, email));
}
do {
g_ptr_array_add (other_emails, talloc_strdup (config, response));
} while (strlen (response));
if (other_emails->len)
- notmuch_config_set_user_other_email (config,
- (const char **)
- other_emails->pdata,
- other_emails->len);
+ notmuch_conffile_set_user_other_email (config,
+ (const char **)
+ other_emails->pdata,
+ other_emails->len);
g_ptr_array_free (other_emails, true);
prompt ("Top-level directory of your email archive [%s]: ",
- notmuch_config_get_database_path (config));
+ notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH));
if (strlen (response)) {
const char *absolute_path;
absolute_path = make_path_absolute (config, response);
- notmuch_config_set_database_path (config, absolute_path);
+ notmuch_conffile_set_database_path (config, absolute_path);
}
- new_tags = notmuch_config_get_new_tags (config, &new_tags_len);
+ new_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_NEW_TAGS);
printf ("Tags to apply to all new messages (separated by spaces) [");
- print_tag_list (new_tags, new_tags_len);
+ print_tag_list (new_tags);
prompt ("]: ");
if (strlen (response)) {
GPtrArray *tags = parse_tag_list (config, response);
- notmuch_config_set_new_tags (config, (const char **) tags->pdata,
- tags->len);
+ notmuch_conffile_set_new_tags (config, (const char **) tags->pdata,
+ tags->len);
g_ptr_array_free (tags, true);
}
-
- search_exclude_tags = notmuch_config_get_search_exclude_tags (config, &search_exclude_tags_len);
+ search_exclude_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_EXCLUDE_TAGS);
printf ("Tags to exclude when searching messages (separated by spaces) [");
- print_tag_list (search_exclude_tags, search_exclude_tags_len);
+ print_tag_list (search_exclude_tags);
prompt ("]: ");
if (strlen (response)) {
GPtrArray *tags = parse_tag_list (config, response);
- notmuch_config_set_search_exclude_tags (config,
- (const char **) tags->pdata,
- tags->len);
+ notmuch_conffile_set_search_exclude_tags (config,
+ (const char **) tags->pdata,
+ tags->len);
g_ptr_array_free (tags, true);
}
- if (notmuch_config_save (config))
+ if (notmuch_conffile_save (config))
return EXIT_FAILURE;
- if (notmuch_config_is_new (config))
+ if (config)
+ notmuch_conffile_close (config);
+
+ if (notmuch_conffile_is_new (config))
welcome_message_post_setup ();
return EXIT_SUCCESS;
return g_mime_content_disposition_get_disposition (disposition);
}
-static bool _get_message_flag (notmuch_message_t *message, notmuch_message_flag_t flag) {
+static bool
+_get_message_flag (notmuch_message_t *message, notmuch_message_flag_t flag)
+{
notmuch_bool_t is_set;
notmuch_status_t status;
status = notmuch_message_get_flag_st (message, flag, &is_set);
if (print_status_message ("notmuch show", message, status))
- INTERNAL_ERROR("unexpected error getting message flag\n");
+ INTERNAL_ERROR ("unexpected error getting message flag\n");
return is_set;
}
}
int i;
+
for (i = 0; i < g_mime_signature_list_length (siglist); i++) {
GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i);
sp->map_key (sp, "decrypted");
sp->begin_map (sp);
sp->map_key (sp, "status");
- sp->string (sp, msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL ? "full" : "partial");
+ sp->string (sp, msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL ?
+ "full" : "partial");
if (msg_crypto->payload_subject) {
const char *subject = g_mime_message_get_subject GMIME_MESSAGE (node->part);
}
if (ssize > 0 && fwrite (buf, ssize, 1, stdout) != 1) {
- fprintf (stderr, "Error: Write %ld chars to stdout failed\n", ssize);
+ fprintf (stderr, "Error: Write %zd chars to stdout failed\n", ssize);
goto DONE;
}
}
notmuch_status_t session_key_count_error = NOTMUCH_STATUS_SUCCESS;
if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
- session_key_count_error = notmuch_message_count_properties (message, "session-key", &session_keys);
+ session_key_count_error = notmuch_message_count_properties (message, "session-key",
+ &session_keys);
status = mime_node_open (local, message, &(params->crypto), &root);
if (status)
part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part));
if (part)
status = format->part (local, sp, part, indent, params);
- if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE && session_key_count_error == NOTMUCH_STATUS_SUCCESS) {
+ if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE && session_key_count_error ==
+ NOTMUCH_STATUS_SUCCESS) {
unsigned int new_session_keys = 0;
- if (notmuch_message_count_properties (message, "session-key", &new_session_keys) == NOTMUCH_STATUS_SUCCESS &&
+ if (notmuch_message_count_properties (message, "session-key", &new_session_keys) ==
+ NOTMUCH_STATUS_SUCCESS &&
new_session_keys > session_keys) {
/* try a quiet re-indexing */
- notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (notmuch_message_get_database (message));
+ notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (
+ notmuch_message_get_database (message));
if (indexopts) {
notmuch_indexopts_set_decrypt_policy (indexopts, NOTMUCH_DECRYPT_AUTO);
print_status_message ("Error re-indexing message with --decrypt=stash",
return 1;
if (count != 1) {
- fprintf (stderr, "Error: search term did not match precisely one message (matched %u messages).\n", count);
+ fprintf (stderr,
+ "Error: search term did not match precisely one message (matched %u messages).\n",
+ count);
return 1;
}
notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, TRUE);
excluded = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED);
- if (!excluded || !params->omit_excluded) {
+ if (! excluded || ! params->omit_excluded) {
status = show_message (ctx, format, sp, message, 0, params);
- if (status && !res)
+ if (status && ! res)
res = status;
} else {
sp->null (sp);
};
int
-notmuch_show_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_show_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
- notmuch_database_t *notmuch;
notmuch_query_t *query;
char *query_string;
int opt_index, ret;
bool entire_thread_set = false;
bool single_message;
bool unthreaded = FALSE;
+ notmuch_status_t status;
notmuch_opt_desc_t options[] = {
{ .opt_keyword = &format, .name = "format", .keywords =
(format != NOTMUCH_FORMAT_TEXT &&
format != NOTMUCH_FORMAT_JSON &&
format != NOTMUCH_FORMAT_SEXP)) {
- fprintf (stderr, "Warning: --include-html only implemented for format=text, format=json and format=sexp\n");
+ fprintf (stderr,
+ "Warning: --include-html only implemented for format=text, format=json and format=sexp\n");
+ }
+
+ if (params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE) {
+ status = notmuch_database_reopen (notmuch, NOTMUCH_DATABASE_MODE_READ_WRITE);
+ if (status) {
+ fprintf (stderr, "Error reopening database for READ_WRITE: %s\n",
+ notmuch_status_to_string (status));
+ return EXIT_FAILURE;
+ }
}
- query_string = query_string_from_args (config, argc - opt_index, argv + opt_index);
+ notmuch_exit_if_unmatched_db_uuid (notmuch);
+
+ query_string = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
if (query_string == NULL) {
fprintf (stderr, "Out of memory\n");
return EXIT_FAILURE;
return EXIT_FAILURE;
}
- notmuch_database_mode_t mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
- if (params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
- mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- mode, ¬much))
- return EXIT_FAILURE;
-
- notmuch_exit_if_unmatched_db_uuid (notmuch);
-
query = notmuch_query_create (notmuch, query_string);
if (query == NULL) {
fprintf (stderr, "Out of memory\n");
/* Create structure printer. */
formatter = formatters[format];
- sprinter = formatter->new_sprinter (config, stdout);
+ sprinter = formatter->new_sprinter (notmuch, stdout);
params.out_stream = g_mime_stream_stdout_new ();
/* If a single message is requested we do not use search_excludes. */
if (single_message) {
- ret = do_show_single (config, query, formatter, sprinter, ¶ms);
+ ret = do_show_single (notmuch, query, formatter, sprinter, ¶ms);
} else {
/* We always apply set the exclude flag. The
* exclude=true|false option controls whether or not we return
* threads that only match in an excluded message */
- const char **search_exclude_tags;
- size_t search_exclude_tags_length;
- unsigned int i;
+ notmuch_config_values_t *exclude_tags;
notmuch_status_t status;
- search_exclude_tags = notmuch_config_get_search_exclude_tags
- (config, &search_exclude_tags_length);
+ for (exclude_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_EXCLUDE_TAGS);
+ notmuch_config_values_valid (exclude_tags);
+ notmuch_config_values_move_to_next (exclude_tags)) {
- for (i = 0; i < search_exclude_tags_length; i++) {
- status = notmuch_query_add_tag_exclude (query, search_exclude_tags[i]);
+ status = notmuch_query_add_tag_exclude (query,
+ notmuch_config_values_get (exclude_tags));
if (status && status != NOTMUCH_STATUS_IGNORED) {
print_status_query ("notmuch show", query, status);
ret = -1;
}
if (unthreaded)
- ret = do_show_unthreaded (config, query, formatter, sprinter, ¶ms);
+ ret = do_show_unthreaded (notmuch, query, formatter, sprinter, ¶ms);
else
- ret = do_show_threaded (config, query, formatter, sprinter, ¶ms);
+ ret = do_show_threaded (notmuch, query, formatter, sprinter, ¶ms);
}
DONE:
}
int
-notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[])
+notmuch_tag_command (notmuch_database_t *notmuch, int argc, char *argv[])
{
tag_op_list_t *tag_ops = NULL;
char *query_string = NULL;
- notmuch_database_t *notmuch;
struct sigaction action;
tag_op_flag_t tag_flags = TAG_FLAG_NONE;
bool batch = false;
const char *input_file_name = NULL;
int opt_index;
int ret;
+ notmuch_bool_t synchronize_flags;
/* Set up our handler for SIGINT */
memset (&action, 0, sizeof (struct sigaction));
return EXIT_FAILURE;
}
} else {
- tag_ops = tag_op_list_create (config);
+ tag_ops = tag_op_list_create (notmuch);
if (tag_ops == NULL) {
fprintf (stderr, "Out of memory.\n");
return EXIT_FAILURE;
}
- if (parse_tag_command_line (config, argc - opt_index, argv + opt_index,
+ if (parse_tag_command_line (notmuch, argc - opt_index, argv + opt_index,
&query_string, tag_ops))
return EXIT_FAILURE;
}
}
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
- return EXIT_FAILURE;
-
notmuch_exit_if_unmatched_db_uuid (notmuch);
- if (notmuch_config_get_maildir_synchronize_flags (config))
+ if (print_status_database (
+ "notmuch restore",
+ notmuch,
+ notmuch_config_get_bool (notmuch, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS,
+ &synchronize_flags)))
+ return EXIT_FAILURE;
+
+ if (synchronize_flags)
tag_flags |= TAG_FLAG_MAILDIR_SYNC;
if (remove_all)
tag_flags |= TAG_FLAG_REMOVE_ALL;
if (batch)
- ret = tag_file (config, notmuch, tag_flags, input);
+ ret = tag_file (notmuch, notmuch, tag_flags, input);
else
- ret = tag_query (config, notmuch, query_string, tag_ops, tag_flags);
+ ret = tag_query (notmuch, notmuch, query_string, tag_ops, tag_flags);
notmuch_database_destroy (notmuch);
*
*/
#define MINUTE (60)
-#define HOUR (60 *MINUTE)
-#define DAY (24 *HOUR)
+#define HOUR (60 * MINUTE)
+#define DAY (24 * HOUR)
#define RELATIVE_DATE_MAX 20
const char *
notmuch_time_relative_date (const void *ctx, time_t then)
*
* The return value will be used as notmuch exit status code,
* preferably EXIT_SUCCESS or EXIT_FAILURE.
+ *
+ * Each subcommand should be passed either a config object, or an open
+ * database
*/
-typedef int (*command_function_t) (notmuch_config_t *config, int argc, char *argv[]);
+typedef int (*command_function_t) (notmuch_database_t *notmuch, int argc, char *argv[]);
typedef struct command {
const char *name;
command_function_t function;
- notmuch_config_mode_t config_mode;
+ notmuch_command_mode_t mode;
const char *summary;
} command_t;
static int
-notmuch_help_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_help_command (notmuch_database_t *notmuch, int argc, char *argv[]);
static int
-notmuch_command (notmuch_config_t *config, int argc, char *argv[]);
+notmuch_command (notmuch_database_t *notmuch, int argc, char *argv[]);
static int
_help_for (const char *topic);
notmuch_status_t status;
if (indexing_cli_choices.opts == NULL)
return NOTMUCH_STATUS_OUT_OF_MEMORY;
- status = notmuch_indexopts_set_decrypt_policy (indexing_cli_choices.opts, indexing_cli_choices.decrypt_policy);
+ status = notmuch_indexopts_set_decrypt_policy (indexing_cli_choices.opts,
+ indexing_cli_choices.decrypt_policy);
if (status != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "Error: Failed to set index decryption policy to %d. (%s)\n",
indexing_cli_choices.decrypt_policy, notmuch_status_to_string (status));
static command_t commands[] = {
- { NULL, notmuch_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
+ { NULL, notmuch_command, NOTMUCH_COMMAND_CONFIG_CREATE | NOTMUCH_COMMAND_CONFIG_LOAD,
"Notmuch main command." },
- { "setup", notmuch_setup_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
+ { "setup", notmuch_setup_command, NOTMUCH_COMMAND_CONFIG_CREATE | NOTMUCH_COMMAND_CONFIG_LOAD,
"Interactively set up notmuch for first use." },
- { "new", notmuch_new_command, NOTMUCH_CONFIG_OPEN,
+ { "new", notmuch_new_command,
+ NOTMUCH_COMMAND_DATABASE_EARLY | NOTMUCH_COMMAND_DATABASE_WRITE |
+ NOTMUCH_COMMAND_DATABASE_CREATE,
"Find and import new messages to the notmuch database." },
- { "insert", notmuch_insert_command, NOTMUCH_CONFIG_OPEN,
+ { "insert", notmuch_insert_command, NOTMUCH_COMMAND_DATABASE_EARLY |
+ NOTMUCH_COMMAND_DATABASE_WRITE,
"Add a new message into the maildir and notmuch database." },
- { "search", notmuch_search_command, NOTMUCH_CONFIG_OPEN,
+ { "search", notmuch_search_command, NOTMUCH_COMMAND_DATABASE_EARLY,
"Search for messages matching the given search terms." },
- { "address", notmuch_address_command, NOTMUCH_CONFIG_OPEN,
+ { "address", notmuch_address_command, NOTMUCH_COMMAND_DATABASE_EARLY,
"Get addresses from messages matching the given search terms." },
- { "show", notmuch_show_command, NOTMUCH_CONFIG_OPEN,
+ { "show", notmuch_show_command, NOTMUCH_COMMAND_DATABASE_EARLY,
"Show all messages matching the search terms." },
- { "count", notmuch_count_command, NOTMUCH_CONFIG_OPEN,
+ { "count", notmuch_count_command, NOTMUCH_COMMAND_DATABASE_EARLY,
"Count messages matching the search terms." },
- { "reply", notmuch_reply_command, NOTMUCH_CONFIG_OPEN,
+ { "reply", notmuch_reply_command, NOTMUCH_COMMAND_DATABASE_EARLY,
"Construct a reply template for a set of messages." },
- { "tag", notmuch_tag_command, NOTMUCH_CONFIG_OPEN,
+ { "tag", notmuch_tag_command, NOTMUCH_COMMAND_DATABASE_EARLY | NOTMUCH_COMMAND_DATABASE_WRITE,
"Add/remove tags for all messages matching the search terms." },
- { "dump", notmuch_dump_command, NOTMUCH_CONFIG_OPEN,
+ { "dump", notmuch_dump_command, NOTMUCH_COMMAND_DATABASE_EARLY | NOTMUCH_COMMAND_DATABASE_WRITE,
"Create a plain-text dump of the tags for each message." },
- { "restore", notmuch_restore_command, NOTMUCH_CONFIG_OPEN,
+ { "restore", notmuch_restore_command, NOTMUCH_COMMAND_DATABASE_EARLY |
+ NOTMUCH_COMMAND_DATABASE_WRITE,
"Restore the tags from the given dump file (see 'dump')." },
- { "compact", notmuch_compact_command, NOTMUCH_CONFIG_OPEN,
+ { "compact", notmuch_compact_command, NOTMUCH_COMMAND_DATABASE_EARLY |
+ NOTMUCH_COMMAND_DATABASE_WRITE,
"Compact the notmuch database." },
- { "reindex", notmuch_reindex_command, NOTMUCH_CONFIG_OPEN,
+ { "reindex", notmuch_reindex_command, NOTMUCH_COMMAND_DATABASE_EARLY |
+ NOTMUCH_COMMAND_DATABASE_WRITE,
"Re-index all messages matching the search terms." },
- { "config", notmuch_config_command, NOTMUCH_CONFIG_OPEN,
+ { "config", notmuch_config_command, NOTMUCH_COMMAND_CONFIG_LOAD,
"Get or set settings in the notmuch configuration file." },
#if WITH_EMACS
{ "emacs-mua", NULL, 0,
"send mail with notmuch and emacs." },
#endif
- { "help", notmuch_help_command, NOTMUCH_CONFIG_CREATE, /* create but don't save config */
+ { "help", notmuch_help_command, NOTMUCH_COMMAND_CONFIG_CREATE, /* create but don't save config */
"This message, or more detailed help for the named command." }
};
notmuch_exit_if_unsupported_format (void)
{
if (notmuch_format_version > NOTMUCH_FORMAT_CUR) {
- fprintf (stderr, "\
+ fprintf (stderr,
+ "\
A caller requested output format version %d, but the installed notmuch\n\
CLI only supports up to format version %d. You may need to upgrade your\n\
notmuch CLI.\n",
notmuch_format_version, NOTMUCH_FORMAT_CUR);
exit (NOTMUCH_EXIT_FORMAT_TOO_NEW);
} else if (notmuch_format_version < NOTMUCH_FORMAT_MIN) {
- fprintf (stderr, "\
+ fprintf (stderr,
+ "\
A caller requested output format version %d, which is no longer supported\n\
by the notmuch CLI (it requires at least version %d). You may need to\n\
upgrade your notmuch front-end.\n",
/* Warn about old version requests so compatibility issues are
* less likely when we drop support for a deprecated format
* versions. */
- fprintf (stderr, "\
+ fprintf (stderr,
+ "\
A caller requested deprecated output format version %d, which may not\n\
be supported in the future.\n", notmuch_format_version);
}
}
static int
-notmuch_help_command (unused (notmuch_config_t *config), int argc, char *argv[])
+notmuch_help_command (unused(notmuch_database_t *notmuch), int argc, char *argv[])
{
int opt_index;
* to be more clever about this in the future.
*/
static int
-notmuch_command (notmuch_config_t *config,
+notmuch_command (notmuch_database_t *notmuch,
unused(int argc), unused(char **argv))
{
- char *db_path;
- struct stat st;
- /* If the user has never configured notmuch, then run
+ const char *config_path;
+
+ /* If the user has not created a configuration file, then run
* notmuch_setup_command which will give a nice welcome message,
* and interactively guide the user through the configuration. */
- if (notmuch_config_is_new (config))
- return notmuch_setup_command (config, 0, NULL);
-
- /* Notmuch is already configured, but is there a database? */
- db_path = talloc_asprintf (config, "%s/%s",
- notmuch_config_get_database_path (config),
- ".notmuch");
- if (stat (db_path, &st)) {
+ config_path = notmuch_config_path (notmuch);
+ if (access (config_path, R_OK | F_OK) == -1) {
if (errno != ENOENT) {
- fprintf (stderr, "Error looking for notmuch database at %s: %s\n",
- db_path, strerror (errno));
+ fprintf (stderr, "Error: %s config file access failed: %s\n", config_path,
+ strerror (errno));
return EXIT_FAILURE;
+ } else {
+ return notmuch_setup_command (notmuch, 0, NULL);
}
- printf ("Notmuch is configured, but there's not yet a database at\n\n\t%s\n\n",
- db_path);
- printf ("You probably want to run \"notmuch new\" now to create that database.\n\n"
- "Note that the first run of \"notmuch new\" can take a very long time\n"
- "and that the resulting database will use roughly the same amount of\n"
- "storage space as the email being indexed.\n\n");
- return EXIT_SUCCESS;
}
printf ("Notmuch is configured and appears to have a database. Excellent!\n\n"
"or any other interface described at https://notmuchmail.org\n\n"
"And don't forget to run \"notmuch new\" whenever new mail arrives.\n\n"
"Have fun, and may your inbox never have much mail.\n\n",
- notmuch_config_get_user_name (config),
- notmuch_config_get_user_primary_email (config));
+ notmuch_config_get (notmuch, NOTMUCH_CONFIG_USER_NAME),
+ notmuch_config_get (notmuch, NOTMUCH_CONFIG_PRIMARY_EMAIL));
return EXIT_SUCCESS;
}
const char *command_name = NULL;
command_t *command;
const char *config_file_name = NULL;
- notmuch_config_t *config = NULL;
+ notmuch_database_t *notmuch = NULL;
int opt_index;
- int ret;
+ int ret = EXIT_SUCCESS;
notmuch_opt_desc_t options[] = {
- { .opt_string = &config_file_name, .name = "config" },
+ { .opt_string = &config_file_name, .name = "config", .allow_empty = TRUE },
{ .opt_inherit = notmuch_shared_options },
{ }
};
goto DONE;
}
- config = notmuch_config_open (local, config_file_name, command->config_mode);
- if (! config) {
- ret = EXIT_FAILURE;
- goto DONE;
+ if (command->mode & NOTMUCH_COMMAND_DATABASE_EARLY) {
+ char *status_string = NULL;
+ notmuch_database_mode_t mode;
+ notmuch_status_t status;
+
+ if (command->mode & NOTMUCH_COMMAND_DATABASE_WRITE ||
+ command->mode & NOTMUCH_COMMAND_DATABASE_CREATE)
+ mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
+ else
+ mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+
+ if (command->mode & NOTMUCH_COMMAND_DATABASE_CREATE) {
+ status = notmuch_database_create_with_config (NULL,
+ config_file_name,
+ NULL,
+ ¬much,
+ &status_string);
+ if (status && status != NOTMUCH_STATUS_DATABASE_EXISTS) {
+ if (status_string) {
+ fputs (status_string, stderr);
+ free (status_string);
+ }
+
+ if (status == NOTMUCH_STATUS_NO_CONFIG)
+ fputs ("Try running 'notmuch setup' to create a configuration.", stderr);
+
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (notmuch == NULL) {
+ status = notmuch_database_open_with_config (NULL,
+ mode,
+ config_file_name,
+ NULL,
+ ¬much,
+ &status_string);
+ if (status) {
+ if (status_string) {
+ fputs (status_string, stderr);
+ free (status_string);
+ }
+
+ return EXIT_FAILURE;
+ }
+ }
}
- ret = (command->function)(config, argc - opt_index, argv + opt_index);
+ if (command->mode & NOTMUCH_COMMAND_CONFIG_LOAD) {
+ char *status_string = NULL;
+ notmuch_status_t status;
+ status = notmuch_database_load_config (NULL,
+ config_file_name,
+ NULL,
+ ¬much,
+ &status_string);
+
+ if (status == NOTMUCH_STATUS_NO_CONFIG && ! (command->mode & NOTMUCH_COMMAND_CONFIG_CREATE)) {
+ fputs ("Try running 'notmuch setup' to create a configuration.", stderr);
+ goto DONE;
+ }
+ switch (status) {
+ case NOTMUCH_STATUS_NO_CONFIG:
+ if (! (command->mode & NOTMUCH_COMMAND_CONFIG_CREATE)) {
+ fputs ("Try running 'notmuch setup' to create a configuration.", stderr);
+ goto DONE;
+ }
+ break;
+ case NOTMUCH_STATUS_NO_DATABASE:
+ if (! command_name) {
+ printf ("Notmuch is configured, but no database was found.\n");
+ printf ("You probably want to run \"notmuch new\" now to create a database.\n\n"
+ "Note that the first run of \"notmuch new\" can take a very long time\n"
+ "and that the resulting database will use roughly the same amount of\n"
+ "storage space as the email being indexed.\n\n");
+ status = NOTMUCH_STATUS_SUCCESS;
+ goto DONE;
+ }
+ break;
+ case NOTMUCH_STATUS_SUCCESS:
+ break;
+ default:
+ goto DONE;
+ }
- DONE:
- if (config)
- notmuch_config_close (config);
+ }
+ ret = (command->function)(notmuch, argc - opt_index, argv + opt_index);
+
+ DONE:
talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
if (talloc_report && strcmp (talloc_report, "") != 0) {
/* this relies on the previous call to
. $(dirname "$0")/perf-test-lib.sh || exit 1
uncache_database
-
time_start
+manifest=$(mktemp manifestXXXXXX)
+find mail -type f ! -path 'mail/.notmuch/*' | sed -n '1~4 p' > $manifest
+xargs tar uf backup.tar < $manifest
+
for i in $(seq 2 6); do
time_run "notmuch new #$i" 'notmuch new'
done
-manifest=$(mktemp manifestXXXXXX)
-
-find mail -type f ! -path 'mail/.notmuch/*' | sed -n '1~4 p' > $manifest
# arithmetic context is to eat extra whitespace on e.g. some BSDs
count=$((`wc -l < $manifest`))
time_run "new ($count mv back)" 'notmuch new'
+perl -nle 'unlink $_; unlink $_.copy' $manifest
+
+time_run "new ($count rm)" 'notmuch new'
+
+tar xf backup.tar
+
+time_run "new ($count restore)" 'notmuch new'
+
perl -nle 'link $_, "$_.copy"' $manifest
time_run "new ($count cp)" 'notmuch new'
{
struct sprinter_json *spj = json_begin_value (sp);
- fprintf (spj->stream, "%"PRId64, val);
+ fprintf (spj->stream, "%" PRId64, val);
}
static void
{
struct sprinter_sexp *sps = sexp_begin_value (sp);
- fprintf (sps->stream, "%"PRId64, val);
+ fprintf (sps->stream, "%" PRId64, val);
}
static void
{
struct sprinter_text *sptxt = (struct sprinter_text *) sp;
- fprintf (sptxt->stream, "%"PRId64, val);
+ fprintf (sptxt->stream, "%" PRId64, val);
}
static void
- gdb(1)
- gpg(1)
- python(1)
+ - xapian-metadata(1)
If your system lacks these tools or have older, non-upgradable versions
of these, please (possibly compile and) install these to some other
notmuch tag -tag1 +tag3 subject:Three
test_begin_subtest "Running compact"
-test_expect_success "notmuch compact --backup=${TEST_DIRECTORY}/xapian.old"
+test_expect_success "notmuch compact --backup=${TMP_DIRECTORY}/xapian.old"
test_begin_subtest "Compact preserves database"
output=$(notmuch search \* | notmuch_search_sanitize)
test_begin_subtest "Restoring Backup"
test_expect_success 'rm -Rf ${MAIL_DIR}/.notmuch/xapian &&
- mv ${TEST_DIRECTORY}/xapian.old ${MAIL_DIR}/.notmuch/xapian'
+ mv ${TMP_DIRECTORY}/xapian.old ${MAIL_DIR}/.notmuch/xapian'
test_begin_subtest "Checking restored backup"
output=$(notmuch search \* | notmuch_search_sanitize)
test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"
test_begin_subtest "Get list value"
-test_expect_equal "$(notmuch config get new.tags)" "\
+cat <<EOF > EXPECTED
+inbox
unread
-inbox"
+EOF
+notmuch config get new.tags | sort > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Set string value"
notmuch config set foo.string "this is a string value"
test_expect_equal "$(notmuch config get foo.nonexistent)" ""
test_begin_subtest "List all items"
-notmuch config list > STDOUT 2> STDERR
-printf "%s\n====\n%s\n" "$(< STDOUT)" "$(< STDERR)" | notmuch_config_sanitize > OUTPUT
-
+notmuch config list 2>&1 | notmuch_config_sanitize > OUTPUT
cat <<EOF > EXPECTED
-database.path=MAIL_DIR
-user.name=Notmuch Test Suite
-user.primary_email=test_suite@notmuchmail.org
-user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
-new.tags=unread;inbox;
-new.ignore=
-search.exclude_tags=
-maildir.synchronize_flags=true
-foo.string=this is another string value
-foo.list=this;is another;list value;
built_with.compact=something
built_with.field_processor=something
built_with.retry_lock=something
-====
-Error opening database at MAIL_DIR/.notmuch: No such file or directory
+database.mail_root=MAIL_DIR
+database.path=MAIL_DIR
+foo.list=this;is another;list value;
+foo.string=this is another string value
+maildir.synchronize_flags=true
+new.ignore=
+new.tags=unread;inbox
+search.exclude_tags=
+user.name=Notmuch Test Suite
+user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
+user.primary_email=test_suite@notmuchmail.org
EOF
test_expect_equal_file EXPECTED OUTPUT
"Another Name"
test_begin_subtest "Top level --config<space>FILE option"
-test_expect_equal "$(notmuch --config alt-config config get user.name)" \
+test_expect_equal "$(notmuch --config alt-config config get user.name)" \
"Another Name"
test_begin_subtest "Top level --config=FILE option changed the right file"
test_begin_subtest "Writing config file through symlink follows symlink"
test_expect_equal "$(readlink alt-config-link)" "alt-config"
+test_begin_subtest "Round trip arbitrary key"
+key=g${RANDOM}.m${RANDOM}
+value=${RANDOM}
+notmuch config set ${key} ${value}
+output=$(notmuch config get ${key})
+test_expect_equal "${output}" "${value}"
+
+test_begin_subtest "Clear arbitrary key"
+notmuch config set ${key}
+output=$(notmuch config get ${key})
+test_expect_equal "${output}" ""
+
+db_path=${HOME}/database-path
+
test_begin_subtest "Absolute database path returned"
notmuch config set database.path ${HOME}/Maildir
test_expect_equal "$(notmuch config get database.path)" \
"${HOME}/Maildir"
-test_begin_subtest "Relative database path properly expanded"
+ln -s `pwd`/mail home/Maildir
+add_email_corpus
+test_begin_subtest "Relative database path expanded"
notmuch config set database.path Maildir
-test_expect_equal "$(notmuch config get database.path)" \
- "${HOME}/Maildir"
+path=$(notmuch config get database.path | notmuch_dir_sanitize)
+count=$(notmuch count '*')
+test_expect_equal "${path} ${count}" \
+ "CWD/home/Maildir 52"
+
+test_begin_subtest "Add config to database"
+notmuch new
+key=g${RANDOM}.m${RANDOM}
+value=${RANDOM}
+notmuch config set --database ${key} ${value}
+notmuch dump --include=config > OUTPUT
+cat <<EOF > EXPECTED
+#notmuch-dump batch-tag:3 config
+#@ ${key} ${value}
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Roundtrip config to/from database"
+notmuch new
+key=g${RANDOM}.m${RANDOM}
+value=${RANDOM}
+notmuch config set --database ${key} ${value}
+output=$(notmuch config get ${key})
+test_expect_equal "${output}" "${value}"
+
+test_begin_subtest "set built_with.* yields error"
+test_expect_code 1 "notmuch config set built_with.compact false"
+
+test_begin_subtest "get built_with.{compact,field_processor} prints true"
+for key in compact field_processor; do
+ notmuch config get built_with.${key}
+done > OUTPUT
+cat <<EOF > EXPECTED
+true
+true
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "get built_with.nonexistent prints false"
+output=$(notmuch config get built_with.nonexistent)
+test_expect_equal "$output" "false"
test_done
--- /dev/null
+#!/usr/bin/env bash
+test_description='Various options for reading configuration'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+backup_config () {
+ local test_name=$(basename $0 .sh)
+ cp ${NOTMUCH_CONFIG} notmuch-config-backup.${test_name}
+}
+
+xdg_config () {
+ local dir
+ local profile=${1:-default}
+ if [[ $profile != default ]]; then
+ export NOTMUCH_PROFILE=$profile
+ fi
+ backup_config
+ dir="${HOME}/.config/notmuch/${profile}"
+ rm -rf $dir
+ mkdir -p $dir
+ CONFIG_PATH=$dir/config
+ mv ${NOTMUCH_CONFIG} ${CONFIG_PATH}
+ unset NOTMUCH_CONFIG
+}
+
+restore_config () {
+ local test_name=$(basename $0 .sh)
+ export NOTMUCH_CONFIG="${TMP_DIRECTORY}/notmuch-config"
+ unset CONFIG_PATH
+ unset NOTMUCH_PROFILE
+ cp notmuch-config-backup.${test_name} ${NOTMUCH_CONFIG}
+}
+
+add_email_corpus
+
+test_begin_subtest "count with saved query from config file"
+backup_config
+query_name="test${RANDOM}"
+notmuch count query:$query_name > OUTPUT
+printf "\n[query]\n${query_name} = tag:inbox\n" >> notmuch-config
+notmuch count query:$query_name >> OUTPUT
+cat <<EOF > EXPECTED
+0
+52
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "count with saved query from config file (xdg)"
+query_name="test${RANDOM}"
+xdg_config
+notmuch count query:$query_name > OUTPUT
+printf "\n[query]\n${query_name} = tag:inbox\n" >> ${CONFIG_PATH}
+notmuch count query:$query_name >> OUTPUT
+cat <<EOF > EXPECTED
+0
+52
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "count with saved query from config file (xdg+profile)"
+query_name="test${RANDOM}"
+xdg_config work
+notmuch count query:$query_name > OUTPUT
+printf "\n[query]\n${query_name} = tag:inbox\n" >> ${CONFIG_PATH}
+notmuch count query:$query_name >> OUTPUT
+cat <<EOF > EXPECTED
+0
+52
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+cat <<EOF > EXPECTED
+Before:
+#notmuch-dump batch-tag:3 tags
+
+After:
+#notmuch-dump batch-tag:3 tags
++attachment +inbox +signed +unread -- id:20091118005829.GB25380@dottiness.seas.harvard.edu
++attachment +inbox +signed +unread -- id:20091118010116.GC25380@dottiness.seas.harvard.edu
++inbox +signed +unread -- id:20091117190054.GU3165@dottiness.seas.harvard.edu
++inbox +signed +unread -- id:20091117203301.GV3165@dottiness.seas.harvard.edu
++inbox +signed +unread -- id:20091118002059.067214ed@hikari
++inbox +signed +unread -- id:20091118005040.GA25380@dottiness.seas.harvard.edu
++inbox +signed +unread -- id:87iqd9rn3l.fsf@vertex.dottedmag
+EOF
+
+test_begin_subtest "dump with saved query from config file"
+backup_config
+query_name="test${RANDOM}"
+CONFIG_PATH=notmuch-config
+printf "Before:\n" > OUTPUT
+notmuch dump --include=tags query:$query_name | sort >> OUTPUT
+printf "\nAfter:\n" >> OUTPUT
+printf "\n[query]\n${query_name} = tag:signed\n" >> ${CONFIG_PATH}
+notmuch dump --include=tags query:$query_name | sort >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "dump with saved query from config file (xdg)"
+backup_config
+query_name="test${RANDOM}"
+xdg_config
+printf "Before:\n" > OUTPUT
+notmuch dump --include=tags query:$query_name | sort >> OUTPUT
+printf "\nAfter:\n" >> OUTPUT
+printf "\n[query]\n${query_name} = tag:signed\n" >> ${CONFIG_PATH}
+notmuch dump --include=tags query:$query_name | sort >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "dump with saved query from config file (xdg+profile)"
+backup_config
+query_name="test${RANDOM}"
+xdg_config work
+printf "Before:\n" > OUTPUT
+notmuch dump --include=tags query:$query_name | sort >> OUTPUT
+printf "\nAfter:\n" >> OUTPUT
+printf "\n[query]\n${query_name} = tag:signed\n" >> ${CONFIG_PATH}
+notmuch dump --include=tags query:$query_name | sort >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "restore with xdg config"
+backup_config
+notmuch dump '*' > EXPECTED
+notmuch tag -inbox '*'
+xdg_config
+notmuch restore --input=EXPECTED
+notmuch dump > OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "restore with xdg+profile config"
+backup_config
+notmuch dump '*' > EXPECTED
+notmuch tag -inbox '*'
+xdg_config work
+notmuch restore --input=EXPECTED
+notmuch dump > OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Insert message with custom new.tags (xdg)"
+backup_config
+xdg_config
+tag=test${RANDOM}
+notmuch --config=${CONFIG_PATH} config set new.tags $tag
+generate_message \
+ "[subject]=\"insert-subject\"" \
+ "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" \
+ "[body]=\"insert-message\""
+mkdir -p ${MAIL_DIR}/{cur,new,tmp}
+notmuch insert < "$gen_msg_filename"
+notmuch dump id:$gen_msg_id > OUTPUT
+cat <<EOF > EXPECTED
+#notmuch-dump batch-tag:3 config,properties,tags
++$tag -- id:$gen_msg_id
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Insert message with custom new.tags (xdg+profile)"
+backup_config
+tag=test${RANDOM}
+xdg_config $tag
+notmuch --config=${CONFIG_PATH} config set new.tags $tag
+generate_message \
+ "[subject]=\"insert-subject\"" \
+ "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" \
+ "[body]=\"insert-message\""
+mkdir -p ${MAIL_DIR}/{cur,new,tmp}
+notmuch insert < "$gen_msg_filename"
+notmuch dump id:$gen_msg_id > OUTPUT
+cat <<EOF > EXPECTED
+#notmuch-dump batch-tag:3 config,properties,tags
++$tag -- id:$gen_msg_id
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reindex with saved query from config file"
+backup_config
+query_name="test${RANDOM}"
+count1=$(notmuch count --lastmod '*' | cut -f3)
+printf "\n[query]\n${query_name} = tag:inbox\n" >> notmuch-config
+notmuch reindex query:$query_name
+count2=$(notmuch count --lastmod '*' | cut -f3)
+restore_config
+test_expect_success "test '$count2 -gt $count1'"
+
+test_begin_subtest "reindex with saved query from config file (xdg)"
+query_name="test${RANDOM}"
+count1=$(notmuch count --lastmod '*' | cut -f3)
+xdg_config
+printf "\n[query]\n${query_name} = tag:inbox\n" >> ${CONFIG_PATH}
+notmuch reindex query:$query_name
+count2=$(notmuch count --lastmod '*' | cut -f3)
+restore_config
+test_expect_success "test '$count2 -gt $count1'"
+
+test_begin_subtest "reindex with saved query from config file (xdg+profile)"
+query_name="test${RANDOM}"
+count1=$(notmuch count --lastmod '*' | cut -f3)
+xdg_config $query_name
+printf "\n[query]\n${query_name} = tag:inbox\n" >> ${CONFIG_PATH}
+notmuch reindex query:$query_name
+count2=$(notmuch count --lastmod '*' | cut -f3)
+restore_config
+test_expect_success "test '$count2 -gt $count1'"
+
+
+
+add_message '[from]="Sender <sender@example.com>"' \
+ [to]=test_suite@notmuchmail.org \
+ '[cc]="Other Parties <cc@example.com>"' \
+ [subject]=notmuch-reply-test \
+ '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+ '[body]="reply with CC"'
+
+cat <<EOF > EXPECTED
+Before:
+After:
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+Cc: Other Parties <cc@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> reply with CC
+EOF
+
+test_begin_subtest "reply with saved query from config file"
+backup_config
+query_name="test${RANDOM}"
+printf "Before:\n" > OUTPUT
+notmuch reply query:$query_name 2>&1 >> OUTPUT
+printf "\n[query]\n${query_name} = id:${gen_msg_id}\n" >> notmuch-config
+printf "After:\n" >> OUTPUT
+notmuch reply query:$query_name >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reply with saved query from config file (xdg)"
+backup_config
+query_name="test${RANDOM}"
+xdg_config
+printf "Before:\n" > OUTPUT
+notmuch reply query:$query_name 2>&1 >> OUTPUT
+printf "\n[query]\n${query_name} = id:${gen_msg_id}\n" >> ${CONFIG_PATH}
+printf "After:\n" >> OUTPUT
+notmuch reply query:$query_name >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reply with saved query from config file (xdg+profile)"
+backup_config
+query_name="test${RANDOM}"
+xdg_config $query_name
+printf "Before:\n" > OUTPUT
+notmuch reply query:$query_name 2>&1 >> OUTPUT
+printf "\n[query]\n${query_name} = id:${gen_msg_id}\n" >> ${CONFIG_PATH}
+printf "After:\n" >> OUTPUT
+notmuch reply query:$query_name >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+backup_database
+test_begin_subtest "search with alternate config"
+notmuch tag -- +foobar17 '*'
+cp notmuch-config alt-config
+notmuch --config=alt-config config set search.exclude_tags foobar17
+output=$(notmuch --config=alt-config count '*')
+test_expect_equal "$output" "0"
+restore_database
+
+cat <<EOF > EXPECTED
+Before:
+After:
+thread:XXX 2009-11-18 [1/2] Carl Worth| Alex Botero-Lowry; [notmuch] [PATCH] Error out if no query is supplied to search instead of going into an infinite loop (attachment inbox unread)
+thread:XXX 2009-11-18 [1/2] Carl Worth| Ingmar Vanhassel; [notmuch] [PATCH] Typsos (inbox unread)
+thread:XXX 2009-11-18 [1/3] Carl Worth| Adrian Perez de Castro, Keith Packard; [notmuch] Introducing myself (inbox signed unread)
+thread:XXX 2009-11-18 [1/3] Carl Worth| Israel Herraiz, Keith Packard; [notmuch] New to the list (inbox unread)
+thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
+thread:XXX 2009-11-18 [1/2] Carl Worth| Jan Janak; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
+thread:XXX 2009-11-18 [1/3(4)] Carl Worth| Aron Griffis, Keith Packard; [notmuch] archive (inbox unread)
+thread:XXX 2009-11-18 [1/2] Carl Worth| Keith Packard; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
+thread:XXX 2009-11-18 [1/7] Carl Worth| Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard; [notmuch] Working with Maildir storage? (inbox signed unread)
+thread:XXX 2009-11-18 [2/5] Carl Worth| Mikhail Gusarov, Keith Packard; [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
+thread:XXX 2009-11-17 [1/2] Carl Worth| Alex Botero-Lowry; [notmuch] preliminary FreeBSD support (attachment inbox unread)
+EOF
+
+test_begin_subtest "search with saved query from config file"
+query_name="test${RANDOM}"
+backup_config
+printf "Before:\n" > OUTPUT
+notmuch search query:$query_name 2>&1 | notmuch_search_sanitize >> OUTPUT
+printf "\n[query]\n${query_name} = from:cworth\n" >> notmuch-config
+printf "After:\n" >> OUTPUT
+notmuch search query:$query_name 2>&1 | notmuch_search_sanitize >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search with saved query from config file (xdg)"
+query_name="test${RANDOM}"
+xdg_config
+printf "Before:\n" > OUTPUT
+notmuch search query:$query_name 2>&1 | notmuch_search_sanitize >> OUTPUT
+printf "\n[query]\n${query_name} = from:cworth\n" >> ${CONFIG_PATH}
+printf "After:\n" >> OUTPUT
+notmuch search query:$query_name 2>&1 | notmuch_search_sanitize >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "search with saved query from config file (xdg + profile)"
+query_name="test${RANDOM}"
+xdg_config $query_name
+printf "Before:\n" > OUTPUT
+notmuch search query:$query_name 2>&1 | notmuch_search_sanitize >> OUTPUT
+printf "\n[query]\n${query_name} = from:cworth\n" >> ${CONFIG_PATH}
+printf "After:\n" >> OUTPUT
+notmuch search query:$query_name 2>&1 | notmuch_search_sanitize >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+cat <<EOF > EXPECTED
+Before:
+After:
+Alex Botero-Lowry <alex.boterolowry@gmail.com>
+Alexander Botero-Lowry <alex.boterolowry@gmail.com>
+François Boulogne <boulogne.f@gmail.com>
+Jjgod Jiang <gzjjgod@gmail.com>
+EOF
+
+test_begin_subtest "address: saved query from config file"
+backup_config
+query_name="test${RANDOM}"
+printf "Before:\n" > OUTPUT
+notmuch address --deduplicate=no --output=sender query:$query_name 2>&1 | sort >> OUTPUT
+printf "\n[query]\n${query_name} = from:gmail.com\n" >> notmuch-config
+printf "After:\n" >> OUTPUT
+notmuch address --output=sender query:$query_name | sort >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "address: saved query from config file (xdg)"
+query_name="test${RANDOM}"
+xdg_config
+printf "Before:\n" > OUTPUT
+notmuch address --deduplicate=no --output=sender query:$query_name 2>&1 | sort >> OUTPUT
+printf "\n[query]\n${query_name} = from:gmail.com\n" >> ${CONFIG_PATH}
+printf "After:\n" >> OUTPUT
+notmuch address --output=sender query:$query_name | sort >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "address: saved query from config file (xdg+profile)"
+query_name="test${RANDOM}"
+xdg_config $query_name
+printf "Before:\n" > OUTPUT
+notmuch address --deduplicate=no --output=sender query:$query_name 2>&1 | sort >> OUTPUT
+printf "\n[query]\n${query_name} = from:gmail.com\n" >> ${CONFIG_PATH}
+printf "After:\n" >> OUTPUT
+notmuch address --output=sender query:$query_name | sort >> OUTPUT
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "show with alternate config"
+backup_database
+cp notmuch-config alt-config
+notmuch --config=alt-config config set search.exclude_tags foobar17
+notmuch tag -- +foobar17 '*'
+output=$(notmuch --config=alt-config show '*' && echo OK)
+restore_database
+test_expect_equal "$output" "OK"
+
+test_begin_subtest "show with alternate config (xdg)"
+backup_database
+notmuch tag -- +foobar17 '*'
+xdg_config
+notmuch --config=${CONFIG_PATH} config set search.exclude_tags foobar17
+output=$(notmuch show '*' && echo OK)
+restore_database
+restore_config
+test_expect_equal "$output" "OK"
+
+test_begin_subtest "show with alternate config (xdg+profile)"
+backup_database
+notmuch tag -- +foobar17 '*'
+xdg_config foobar17
+notmuch --config=${CONFIG_PATH} config set search.exclude_tags foobar17
+output=$(notmuch show '*' && echo OK)
+restore_database
+restore_config
+test_expect_equal "$output" "OK"
+
+# reset to known state
+add_email_corpus
+
+test_begin_subtest "tag with saved query from config file"
+backup_config
+query_name="test${RANDOM}"
+tag_name="tag${RANDOM}"
+notmuch count query:$query_name > OUTPUT
+printf "\n[query]\n${query_name} = tag:inbox\n" >> notmuch-config
+notmuch tag +$tag_name -- query:${query_name}
+notmuch count tag:$tag_name >> OUTPUT
+cat <<EOF > EXPECTED
+0
+52
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "tag with saved query from config file (xdg)"
+xdg_config
+query_name="test${RANDOM}"
+tag_name="tag${RANDOM}"
+notmuch count query:$query_name > OUTPUT
+printf "\n[query]\n${query_name} = tag:inbox\n" >> ${CONFIG_PATH}
+notmuch tag +$tag_name -- query:${query_name}
+notmuch count tag:$tag_name >> OUTPUT
+cat <<EOF > EXPECTED
+0
+52
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "tag with saved query from config file (xdg+profile)"
+query_name="test${RANDOM}"
+xdg_config ${query_name}
+tag_name="tag${RANDOM}"
+notmuch count query:$query_name > OUTPUT
+printf "\n[query]\n${query_name} = tag:inbox\n" >> ${CONFIG_PATH}
+notmuch tag +$tag_name -- query:${query_name}
+notmuch count tag:$tag_name >> OUTPUT
+cat <<EOF > EXPECTED
+0
+52
+EOF
+restore_config
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "running compact (xdg)"
+xdg_config
+notmuch compact
+output=$(notmuch count '*')
+restore_config
+test_expect_equal "52" "$output"
+
+test_begin_subtest "running compact (xdg + profile)"
+xdg_config ${RANDOM}
+notmuch compact
+output=$(notmuch count '*')
+restore_config
+test_expect_equal "52" "$output"
+
+test_begin_subtest "run notmuch-new (xdg)"
+xdg_config
+generate_message
+output=$(NOTMUCH_NEW --debug)
+restore_config
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_begin_subtest "run notmuch-new (xdg + profile)"
+xdg_config ${RANDOM}
+generate_message
+output=$(NOTMUCH_NEW --debug)
+restore_config
+test_expect_equal "$output" "Added 1 new message to the database."
+
+test_done
test_begin_subtest "Notmuch new without a config suggests notmuch setup"
output=$(notmuch --config=new-notmuch-config new 2>&1)
test_expect_equal "$output" "\
-Configuration file new-notmuch-config not found.
+Error: cannot load config file.
Try running 'notmuch setup' to create a configuration."
test_begin_subtest "Create a new config interactively"
-notmuch --config=new-notmuch-config > /dev/null <<EOF
+notmuch --config=new-notmuch-config > log.${test_count} <<EOF
Test Suite
test.suite@example.com
another.suite@example.com
baz
EOF
-output=$(notmuch --config=new-notmuch-config config list | notmuch_built_with_sanitize)
-test_expect_equal "$output" "\
-database.path=/path/to/maildir
-user.name=Test Suite
-user.primary_email=test.suite@example.com
-user.other_email=another.suite@example.com;
-new.tags=foo;bar;
-new.ignore=
-search.exclude_tags=baz;
-maildir.synchronize_flags=true
-built_with.compact=something
-built_with.field_processor=something
-built_with.retry_lock=something"
+expected_dir=$NOTMUCH_SRCDIR/test/setup.expected-output
+test_expect_equal_file ${expected_dir}/config-with-comments new-notmuch-config
+
+test_begin_subtest "notmuch with a config but without a database suggests notmuch new"
+notmuch 2>&1 | notmuch_dir_sanitize > OUTPUT
+cat <<EOF > EXPECTED
+Notmuch is configured, but no database was found.
+You probably want to run "notmuch new" now to create a database.
+
+Note that the first run of "notmuch new" can take a very long time
+and that the resulting database will use roughly the same amount of
+storage space as the email being indexed.
+
+EOF
+test_expect_equal_file EXPECTED OUTPUT
test_done
OLDCONFIG=$(notmuch config get new.tags)
-test_begin_subtest "Empty tags in new.tags are forbidden"
+test_begin_subtest "Empty tags in new.tags are ignored"
notmuch config set new.tags "foo;;bar"
-output=$(NOTMUCH_NEW --debug 2>&1)
-test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden"
+output=$(NOTMUCH_NEW --quiet 2>&1)
+test_expect_equal "$output" ""
test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
notmuch config set new.tags "-foo;bar"
notmuch config set new.tags $OLDCONFIG
+test_begin_subtest "Long directory names don't cause rescan"
+test_subtest_known_broken
+printf -v name 'z%.0s' {1..234}
+generate_message [dir]=$name
+NOTMUCH_NEW > OUTPUT
+notmuch new >> OUTPUT
+rm -r ${MAIL_DIR}/${name}
+notmuch new >> OUTPUT
+cat <<EOF > EXPECTED
+Added 1 new message to the database.
+No new mail.
+No new mail. Removed 1 message.
+EOF
+test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "Xapian exception: read only files"
-chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
+chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
output=$(NOTMUCH_NEW --debug 2>&1 | sed 's/: .*$//' )
-chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
+chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
test_expect_equal "$output" "A Xapian exception occurred opening database"
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "Relative database path expanded in new"
+ln -s "$PWD/mail" home/Maildir
+notmuch config set database.path Maildir
+generate_message
+NOTMUCH_NEW > OUTPUT
+cat <<EOF >EXPECTED
+Added 1 new message to the database.
+EOF
+notmuch config set database.path ${MAIL_DIR}
+rm home/Maildir
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "Relative mail root (in db) expanded in new"
+ln -s "$PWD/mail" home/Maildir
+notmuch config set --database database.mail_root Maildir
+generate_message
+NOTMUCH_NEW > OUTPUT
+cat <<EOF >EXPECTED
+Added 1 new message to the database.
+EOF
+notmuch config set database.mail_root
+rm home/Maildir
+test_expect_equal_file EXPECTED OUTPUT
+
add_email_corpus broken
test_begin_subtest "reference loop does not crash"
test_expect_code 0 "notmuch show --format=json id:mid-loop-12@example.org id:mid-loop-21@example.org > OUTPUT"
test_begin_subtest "reference loop ordered by date"
-threadid=$(notmuch search --output=threads id:mid-loop-12@example.org)
-notmuch show --format=mbox $threadid | grep '^Date' > OUTPUT
+threadid=$(notmuch search --output=threads id:mid-loop-12@example.org)
+notmuch show --format=mbox $threadid | grep '^Date' > OUTPUT
cat <<EOF > EXPECTED
Date: Thu, 16 Jun 2016 22:14:41 -0400
Date: Fri, 17 Jun 2016 22:14:41 -0400
--- /dev/null
+#!/usr/bin/env bash
+test_description='Configuration of mail-root and database path'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_require_external_prereq xapian-metdata
+
+backup_config () {
+ local test_name=$(basename $0 .sh)
+ cp ${NOTMUCH_CONFIG} notmuch-config-backup.${test_name}
+}
+
+restore_config () {
+ local test_name=$(basename $0 .sh)
+ export NOTMUCH_CONFIG="${TMP_DIRECTORY}/notmuch-config"
+ unset CONFIG_PATH
+ unset DATABASE_PATH
+ unset NOTMUCH_PROFILE
+ unset XAPIAN_PATH
+ unset MAILDIR
+ rm -f "$HOME/mail"
+ cp notmuch-config-backup.${test_name} ${NOTMUCH_CONFIG}
+}
+
+split_config () {
+ local dir
+ backup_config
+ dir="$TMP_DIRECTORY/database.$test_count"
+ rm -rf $dir
+ mkdir $dir
+ notmuch config set database.path $dir
+ notmuch config set database.mail_root $MAIL_DIR
+ DATABASE_PATH=$dir
+ XAPIAN_PATH="$dir/xapian"
+}
+
+symlink_config () {
+ local dir
+ backup_config
+ dir="$TMP_DIRECTORY/link.$test_count"
+ ln -s $MAIL_DIR $dir
+ notmuch config set database.path $dir
+ notmuch config set database.mail_root $MAIL_DIR
+ XAPIAN_PATH="$MAIL_DIR/.notmuch/xapian"
+ unset DATABASE_PATH
+}
+
+
+home_mail_config () {
+ local dir
+ backup_config
+ dir="${HOME}/mail"
+ ln -s $MAIL_DIR $dir
+ notmuch config set database.path
+ notmuch config set database.mail_root
+ XAPIAN_PATH="$MAIL_DIR/.notmuch/xapian"
+ unset DATABASE_PATH
+}
+
+maildir_env_config () {
+ local dir
+ backup_config
+ dir="${HOME}/env_points_here"
+ ln -s $MAIL_DIR $dir
+ export MAILDIR=$dir
+ notmuch config set database.path
+ notmuch config set database.mail_root
+ XAPIAN_PATH="${MAIL_DIR}/.notmuch/xapian"
+ unset DATABASE_PATH
+}
+
+xdg_config () {
+ local dir
+ local profile=${1:-default}
+
+ if [[ $profile != default ]]; then
+ export NOTMUCH_PROFILE=$profile
+ fi
+
+ backup_config
+ DATABASE_PATH="${HOME}/.local/share/notmuch/${profile}"
+ rm -rf $DATABASE_PATH
+ mkdir -p $DATABASE_PATH
+
+ config_dir="${HOME}/.config/notmuch/${profile}"
+ mkdir -p ${config_dir}
+ CONFIG_PATH=$config_dir/config
+ mv ${NOTMUCH_CONFIG} $CONFIG_PATH
+ unset NOTMUCH_CONFIG
+
+ XAPIAN_PATH="${DATABASE_PATH}/xapian"
+ notmuch --config=${CONFIG_PATH} config set database.mail_root ${TMP_DIRECTORY}/mail
+ notmuch --config=${CONFIG_PATH} config set database.path
+}
+
+for config in traditional split XDG XDG+profile symlink home_mail maildir_env; do
+ #start each set of tests with an known set of messages
+ add_email_corpus
+
+ case $config in
+ traditional)
+ backup_config
+ XAPIAN_PATH="$MAIL_DIR/.notmuch/xapian"
+ ;;
+ split)
+ split_config
+ mv mail/.notmuch/xapian $DATABASE_PATH
+ ;;
+ XDG)
+ xdg_config
+ mv mail/.notmuch/xapian $DATABASE_PATH
+ ;;
+ XDG+profile)
+ xdg_config ${RANDOM}
+ mv mail/.notmuch/xapian $DATABASE_PATH
+ ;;
+ symlink)
+ symlink_config
+ ;;
+ home_mail)
+ home_mail_config
+ ;;
+ maildir_env)
+ maildir_env_config
+ ;;
+ esac
+
+ test_begin_subtest "count ($config)"
+ output=$(notmuch count '*')
+ test_expect_equal "$output" '52'
+
+ test_begin_subtest "count+tag ($config)"
+ tag="tag${RANDOM}"
+ notmuch tag +$tag '*'
+ output=$(notmuch count tag:$tag)
+ notmuch tag -$tag '*'
+ test_expect_equal "$output" '52'
+
+ test_begin_subtest "address ($config)"
+ notmuch address --deduplicate=no --sort=newest-first --output=sender --output=recipients path:foo >OUTPUT
+ cat <<EOF >EXPECTED
+Carl Worth <cworth@cworth.org>
+notmuch@notmuchmail.org
+EOF
+ test_expect_equal_file EXPECTED OUTPUT
+
+ test_begin_subtest "dump ($config)"
+ notmuch dump is:attachment and is:signed | sort > OUTPUT
+ cat <<EOF > EXPECTED
+#notmuch-dump batch-tag:3 config,properties,tags
++attachment +inbox +signed +unread -- id:20091118005829.GB25380@dottiness.seas.harvard.edu
++attachment +inbox +signed +unread -- id:20091118010116.GC25380@dottiness.seas.harvard.edu
+EOF
+ test_expect_equal_file EXPECTED OUTPUT
+
+ test_begin_subtest "dump + tag + restore ($config)"
+ notmuch dump '*' > EXPECTED
+ notmuch tag -inbox '*'
+ notmuch restore < EXPECTED
+ notmuch dump > OUTPUT
+ test_expect_equal_file EXPECTED OUTPUT
+
+ test_begin_subtest "reindex ($config)"
+ notmuch search --output=messages '*' > EXPECTED
+ notmuch reindex '*'
+ notmuch search --output=messages '*' > OUTPUT
+ test_expect_equal_file EXPECTED OUTPUT
+
+ test_begin_subtest "use existing database ($config)"
+ output=$(notmuch new)
+ test_expect_equal "$output" 'No new mail.'
+
+ test_begin_subtest "create database ($config)"
+ rm -rf $DATABASE_PATH/{.notmuch,}/xapian
+ notmuch new
+ output=$(notmuch count '*')
+ test_expect_equal "$output" '52'
+
+ test_begin_subtest "detect new files ($config)"
+ generate_message
+ generate_message
+ notmuch new
+ output=$(notmuch count '*')
+ test_expect_equal "$output" '54'
+
+ test_begin_subtest "Show a raw message ($config)"
+ add_message
+ notmuch show --format=raw id:$gen_msg_id > OUTPUT
+ test_expect_equal_file $gen_msg_filename OUTPUT
+ rm -f $gen_msg_filename
+
+ test_begin_subtest "reply ($config)"
+ add_message '[from]="Sender <sender@example.com>"' \
+ [to]=test_suite@notmuchmail.org \
+ [subject]=notmuch-reply-test \
+ '[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
+ '[body]="basic reply test"'
+ notmuch reply id:${gen_msg_id} 2>&1 > OUTPUT
+ cat <<EOF > EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+Subject: Re: notmuch-reply-test
+To: Sender <sender@example.com>
+In-Reply-To: <${gen_msg_id}>
+References: <${gen_msg_id}>
+
+On Tue, 05 Jan 2010 15:43:56 -0000, Sender <sender@example.com> wrote:
+> basic reply test
+EOF
+ test_expect_equal_file EXPECTED OUTPUT
+
+ test_begin_subtest "insert+search ($config)"
+ generate_message \
+ "[subject]=\"insert-subject\"" \
+ "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" \
+ "[body]=\"insert-message\""
+ mkdir -p "$MAIL_DIR"/{cur,new,tmp}
+ notmuch insert < "$gen_msg_filename"
+ cur_msg_filename=$(notmuch search --output=files "subject:insert-subject")
+ test_expect_equal_file "$cur_msg_filename" "$gen_msg_filename"
+
+
+ test_begin_subtest "compact+search ($config)"
+ notmuch search --output=messages '*' | sort > EXPECTED
+ notmuch compact
+ notmuch search --output=messages '*' | sort > OUTPUT
+ test_expect_equal_file EXPECTED OUTPUT
+
+ test_begin_subtest "upgrade backup ($config)"
+ features=$(xapian-metadata get $XAPIAN_PATH features | grep -v "^relative directory paths")
+ xapian-metadata set $XAPIAN_PATH features "$features"
+ output=$(notmuch new | grep Welcome)
+ test_expect_equal \
+ "$output" \
+ "Welcome to a new version of notmuch! Your database will now be upgraded."
+
+ test_begin_subtest "notmuch +config -database suggests notmuch new ($config)"
+ mv "$XAPIAN_PATH" "${XAPIAN_PATH}.bak"
+ notmuch > OUTPUT
+cat <<EOF > EXPECTED
+Notmuch is configured, but no database was found.
+You probably want to run "notmuch new" now to create a database.
+
+Note that the first run of "notmuch new" can take a very long time
+and that the resulting database will use roughly the same amount of
+storage space as the email being indexed.
+
+EOF
+ mv "${XAPIAN_PATH}.bak" "$XAPIAN_PATH"
+
+ test_expect_equal_file EXPECTED OUTPUT
+
+ test_begin_subtest "Set config value ($config)"
+ name=${RANDOM}
+ value=${RANDOM}
+ notmuch config set test${test_count}.${name} ${value}
+ output=$(notmuch config get test${test_count}.${name})
+ notmuch config set test${test_count}.${name}
+ output2=$(notmuch config get test${test_count}.${name})
+ test_expect_equal "${output}+${output2}" "${value}+"
+
+ test_begin_subtest "Set config value in database ($config)"
+ name=${RANDOM}
+ value=${RANDOM}
+ notmuch config set --database test${test_count}.${name} ${value}
+ output=$(notmuch config get test${test_count}.${name})
+ notmuch config set --database test${test_count}.${name}
+ output2=$(notmuch config get test${test_count}.${name})
+ test_expect_equal "${output}+${output2}" "${value}+"
+
+ test_begin_subtest "Config list ($config)"
+ notmuch config list | notmuch_dir_sanitize | \
+ sed -e "s/^database.backup_dir=.*$/database.backup_dir/" \
+ -e "s/^database.hook_dir=.*$/database.hook_dir/" \
+ -e "s/^database.path=.*$/database.path/" \
+ -e "s,^database.mail_root=CWD/home/mail,database.mail_root=MAIL_DIR," \
+ -e "s,^database.mail_root=CWD/home/env_points_here,database.mail_root=MAIL_DIR," \
+ > OUTPUT
+ cat <<EOF > EXPECTED
+built_with.compact=true
+built_with.field_processor=true
+built_with.retry_lock=true
+database.backup_dir
+database.hook_dir
+database.mail_root=MAIL_DIR
+database.path
+maildir.synchronize_flags=true
+new.ignore=
+new.tags=unread;inbox
+search.exclude_tags=
+user.name=Notmuch Test Suite
+user.other_email=test_suite_other@notmuchmail.org;test_suite@otherdomain.org
+user.primary_email=test_suite@notmuchmail.org
+EOF
+ test_expect_equal_file EXPECTED OUTPUT
+
+ case $config in
+ XDG*)
+ test_begin_subtest "Set shadowed config value in database ($config)"
+ name=${RANDOM}
+ value=${RANDOM}
+ key=test${test_count}.${name}
+ notmuch config set --database ${key} ${value}
+ notmuch config set ${key} shadow${value}
+ output=$(notmuch --config='' config get ${key})
+ notmuch config set --database ${key}
+ output2=$(notmuch --config='' config get ${key})
+ notmuch config set ${key}
+ test_expect_equal "${output}+${output2}" "${value}+"
+ ;;
+ esac
+ restore_config
+ rm -rf home/.local
+ rm -rf home/.config
+done
+
+test_done
OLDCONFIG=$(notmuch config get new.tags)
-test_begin_subtest "Empty tags in new.tags are forbidden"
+test_begin_subtest "Empty tags in new.tags are ignored"
notmuch config set new.tags "foo;;bar"
gen_insert_msg
-output=$(notmuch insert < $gen_msg_filename 2>&1)
-test_expect_equal "$output" "Error: tag '' in new.tags: empty tag forbidden"
+notmuch insert < $gen_msg_filename
+output=$(notmuch show --format=json id:$gen_msg_id)
+test_json_nodes <<<"$output" \
+ 'new_tags:[0][0][0]["tags"] = ["bar", "foo"]'
test_begin_subtest "Tags starting with '-' in new.tags are forbidden"
notmuch config set new.tags "-foo;bar"
# DUPLICATE_MESSAGE_ID is not tested here, because it should actually pass.
# pregenerate all of the test shims
-for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR OUT_OF_MEMORY XAPIAN_EXCEPTION; do
+for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR OUT_OF_MEMORY XAPIAN_EXCEPTION; do
make_shim shim-$code <<EOF
#include <notmuch.h>
#include <stdio.h>
gen_insert_msg
-for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
+for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
test_begin_subtest "EXIT_FAILURE when index_file returns $code"
test_expect_code 1 "notmuch_with_shim shim-$code insert < \"$gen_msg_filename\""
output=$(notmuch search subject:deleted | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)"
+test_begin_subtest "Search, exclude \"deleted\" messages; alternate config file"
+cp ${NOTMUCH_CONFIG} alt-config
+notmuch config set search.exclude_tags
+notmuch --config=alt-config search subject:deleted | notmuch_search_sanitize > OUTPUT
+cp alt-config ${NOTMUCH_CONFIG}
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_begin_subtest "Search, exclude \"deleted\" messages from message search"
output=$(notmuch search --output=messages subject:deleted | notmuch_search_sanitize)
test_expect_equal "$output" "id:$not_deleted_id"
output=$(notmuch count --output=threads tag:test and tag:deleted)
test_expect_equal "$output" "3"
+test_begin_subtest "Count, default exclusion, batch"
+notmuch count --batch --output=messages<<EOF > OUTPUT
+tag:test
+tag:test and tag:deleted
+tag:test
+tag:test and tag:deleted
+EOF
+cat <<EOF >EXPECTED
+2
+4
+2
+4
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_begin_subtest "Count, exclude=true: tag in query (messages)"
output=$(notmuch count --exclude=true tag:test and tag:deleted)
test_expect_equal "$output" "4"
Subject: No messages excluded: single match: reply 5"
test_begin_subtest "Show, exclude=false"
-output=$(notmuch show --exclude=false tag:test | notmuch_show_sanitize_all | egrep "Subject:|message{")
+output=$(notmuch show --exclude=false tag:test | notmuch_show_sanitize_all | egrep "Subject:|message{")
test_expect_equal "$output" "\fmessage{ id:XXXXX depth:0 match:1 excluded:1 filename:XXXXX
Subject: All messages excluded: single match: reply 2
\fmessage{ id:XXXXX depth:0 match:1 excluded:1 filename:XXXXX
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Two (inbox tag4 tag5 unread)"
# generate a common input file for the next several tests.
-cat > batch.in <<EOF
+cat > batch.in <<EOF
# %40 is an @ in tag
+%40 -tag5 +tag6 -- One
+tag1 -tag1 -tag4 +tag4 -- Two
test_expect_code 1 'notmuch tag +- One'
test_begin_subtest "Xapian exception: read only files"
-chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
+chmod u-w ${MAIL_DIR}/.notmuch/xapian/*.*
output=$(notmuch tag +something '*' 2>&1 | sed 's/: .*$//' )
-chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
+chmod u+w ${MAIL_DIR}/.notmuch/xapian/*.*
test_expect_equal "$output" "A Xapian exception occurred opening database"
test_done
notmuch show --format=raw subject:$size > OUTPUT
test_expect_equal_file mail/size-$size OUTPUT
test_begin_subtest "return value, message of size $size"
- test_expect_success "notmuch show --format=raw subject:$size > /dev/null"
+ test_expect_success "notmuch show --format=raw subject:$size > /dev/null"
done
test_done
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="Multiple recipients"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <sender@example.com>
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="From Us, Multiple TO recipients"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Recipient <recipient@example.com>, Someone Else <someone@example.com>
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="reply with CC"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <sender@example.com>
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="reply with CC"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Recipient <recipient@example.com>
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="reply with CC"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
Cc: Other Parties <cc@example.com>
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="reply from alternate address"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite_other@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <sender@example.com>
'[body]="support for reply-to"' \
'[reply-to]="Sender <elsewhere@example.com>"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <elsewhere@example.com>
'[body]="support for reply-to with multiple recipients"' \
'[reply-to]="Sender <elsewhere@example.com>"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <elsewhere@example.com>
'[body]="Un-munging Reply-To"' \
'[reply-to]="Evil Munging List <list@example.com>"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: notmuch-reply-test
To: Sender <sender@example.com>
add_message '[subject]="This subject is exactly 200 bytes in length. Other than its length there is not much of note here. Note that the length of 200 bytes includes the Subject: and Re: prefixes with two spaces"' \
'[date]="Tue, 05 Jan 2010 15:43:56 -0000"' \
'[body]="200-byte header"'
-output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
+output=$(notmuch reply --reply-to=sender id:${gen_msg_id})
test_expect_equal "$output" "From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: This subject is exactly 200 bytes in length. Other than its
length there is not much of note here. Note that the length of 200 bytes
test_expect_success \
'notmuch restore --input=dump.expected &&
notmuch restore --accumulate --input=dump-ABC_DEF.expected &&
- notmuch dump > OUTPUT.$test_count &&
+ notmuch dump > OUTPUT.$test_count &&
notmuch restore --input=dump.expected &&
test_cmp dump-ABC_DEF.expected OUTPUT.$test_count'
test_begin_subtest 'format=batch-tag, checking encoded output'
NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth |\
awk "{ print \"+$enc1 +$enc2 +$enc3 -- \" \$5 }" > EXPECTED.$test_count
-NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth > OUTPUT.$test_count
+NOTMUCH_DUMP_TAGS --format=batch-tag -- from:cworth > OUTPUT.$test_count
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
test_begin_subtest 'restoring sane tags'
test_expect_equal_file EXPECTED OUTPUT
+backup_database
test_begin_subtest 'roundtripping random message-ids and tags'
${TEST_DIRECTORY}/random-corpus --config-path=${NOTMUCH_CONFIG} \
sort > OUTPUT.$test_count
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
+restore_database
test_done
-# Note the database is "poisoned" for sup format at this point.
output=$(notmuch search $os_x_darwin_thread | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2009-11-18 [4/4] Jjgod Jiang, Alexander Botero-Lowry; [notmuch] Mac OS X/Darwin compatibility issues (inbox unread)"
+test_begin_subtest "notmuch-show: before-tag-hook is run, variables are defined"
+output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
+ (notmuch-before-tag-hook (function notmuch-test-tag-hook)))
+ (notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
+ (execute-kbd-macro "+activate-hook\n")
+ (execute-kbd-macro "-activate-hook\n")
+ notmuch-test-tag-hook-output)')
+test_expect_equal "$output" \
+'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
+ ("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
+
+test_begin_subtest "notmuch-show: after-tag-hook is run, variables are defined"
+output=$(test_emacs '(let ((notmuch-test-tag-hook-output nil)
+ (notmuch-after-tag-hook (function notmuch-test-tag-hook)))
+ (notmuch-show "id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com")
+ (execute-kbd-macro "+activate-hook\n")
+ (execute-kbd-macro "-activate-hook\n")
+ notmuch-test-tag-hook-output)')
+test_expect_equal "$output" \
+'(("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "-activate-hook")
+ ("id:ddd65cda0911171950o4eea4389v86de9525e46052d3@mail.gmail.com" "+activate-hook"))'
+
test_begin_subtest "Message with .. in Message-Id:"
add_message [id]=123..456@example '[subject]="Message with .. in Message-Id"'
test_emacs '(notmuch-search "id:\"123..456@example\"")
test_begin_subtest "Tag changes modify flags of multiple files"
notmuch tag -replied subject:"Duplicated message"
(cd $MAIL_DIR/cur/; ls duplicated*) > actual
-test_expect_equal "$(< actual)" "duplicated-message-another-copy:2,S
+test_expect_equal "$(< actual)" "duplicated-message-another-copy:2,S
duplicated-message-copy:2,S
duplicated-message:2,S"
| notmuch_dir_sanitize | sed -e "s,\`,\',g" -e "s,No [^[:space:]]* database,No XXXXXX database,g" > OUTPUT
cat <<EOF > EXPECTED
-A Xapian exception occurred opening database: Couldn't stat 'CWD/fakedb/.notmuch/xapian'
+Cannot open Xapian database at CWD/fakedb/.notmuch/xapian: Couldn't stat 'CWD/fakedb/.notmuch/xapian'
caught No XXXXXX database found at path 'CWD/nonexistent'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "python cffi tests"
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 --log-file=$TMP_DIRECTORY/test.output)"
+test_expect_success "(cd $pytest_dir && ${NOTMUCH_PYTHON} -m pytest --verbose --log-file=$TMP_DIRECTORY/test.output)"
test_done
test_description='hooks'
. $(dirname "$0")/test-lib.sh || exit 1
-HOOK_DIR=${MAIL_DIR}/.notmuch/hooks
+test_require_external_prereq xapian-delve
create_echo_hook () {
local TOKEN="${RANDOM}"
echo "${TOKEN}" > ${2}
}
-create_failing_hook () {
+create_write_hook () {
+ local TOKEN="${RANDOM}"
mkdir -p ${HOOK_DIR}
cat <<EOF >"${HOOK_DIR}/${1}"
#!/bin/sh
-exit 13
+if xapian-delve ${MAIL_DIR}/.notmuch/xapian | grep -q "writing = false"; then
+ echo "${TOKEN}" > ${3}
+fi
EOF
chmod +x "${HOOK_DIR}/${1}"
+ echo "${TOKEN}" > ${2}
}
-rm_hooks () {
- rm -rf ${HOOK_DIR}
+create_change_hook () {
+ mkdir -p ${HOOK_DIR}
+ cat <<EOF >"${HOOK_DIR}/${1}"
+#!/bin/sh
+notmuch insert --no-hooks < ${2} > /dev/null
+rm -f ${2}
+EOF
+ chmod +x "${HOOK_DIR}/${1}"
+}
+
+create_failing_hook () {
+ local HOOK_DIR=${2}
+ mkdir -p ${HOOK_DIR}
+ cat <<EOF >"${HOOK_DIR}/${1}"
+#!/bin/sh
+exit 13
+EOF
+ chmod +x "${HOOK_DIR}/${1}"
}
# add a message to generate mail dir and database
# create maildir structure for notmuch-insert
mkdir -p "$MAIL_DIR"/{cur,new,tmp}
-test_begin_subtest "pre-new is run"
-rm_hooks
-generate_message
-create_echo_hook "pre-new" expected output
-notmuch new > /dev/null
-test_expect_equal_file expected output
-
-test_begin_subtest "post-new is run"
-rm_hooks
-generate_message
-create_echo_hook "post-new" expected output
-notmuch new > /dev/null
-test_expect_equal_file expected output
-
-test_begin_subtest "post-insert hook is run"
-rm_hooks
-generate_message
-create_echo_hook "post-insert" expected output
-notmuch insert < "$gen_msg_filename"
-test_expect_equal_file expected output
-
-test_begin_subtest "pre-new is run before post-new"
-rm_hooks
-generate_message
-create_echo_hook "pre-new" pre-new.expected pre-new.output
-create_echo_hook "post-new" post-new.expected post-new.output
-notmuch new > /dev/null
-test_expect_equal_file post-new.expected post-new.output
-
-test_begin_subtest "pre-new non-zero exit status (hook status)"
-rm_hooks
-generate_message
-create_failing_hook "pre-new"
-output=`notmuch new 2>&1`
-test_expect_equal "$output" "Error: pre-new hook failed with status 13"
-
-# depends on the previous subtest leaving broken hook behind
-test_begin_subtest "pre-new non-zero exit status (notmuch status)"
-test_expect_code 1 "notmuch new"
-
-# depends on the previous subtests leaving 1 new message behind
-test_begin_subtest "pre-new non-zero exit status aborts new"
-rm_hooks
-output=$(NOTMUCH_NEW)
-test_expect_equal "$output" "Added 1 new message to the database."
-
-test_begin_subtest "post-new non-zero exit status (hook status)"
-rm_hooks
-generate_message
-create_failing_hook "post-new"
-NOTMUCH_NEW 2>output.stderr >output
-cat output.stderr >> output
-echo "Added 1 new message to the database." > expected
-echo "Error: post-new hook failed with status 13" >> expected
-test_expect_equal_file expected output
-
-# depends on the previous subtest leaving broken hook behind
-test_begin_subtest "post-new non-zero exit status (notmuch status)"
-test_expect_code 1 "notmuch new"
-
-test_begin_subtest "post-insert hook does not affect insert status"
-rm_hooks
-generate_message
-create_failing_hook "post-insert"
-test_expect_success "notmuch insert < \"$gen_msg_filename\" > /dev/null"
-
-test_begin_subtest "hook without executable permissions"
-rm_hooks
-mkdir -p ${HOOK_DIR}
-cat <<EOF >"${HOOK_DIR}/pre-new"
-#!/bin/sh
-echo foo
+for config in traditional profile explicit relative XDG split; do
+ unset NOTMUCH_PROFILE
+ notmuch config set database.hook_dir
+ notmuch config set database.path ${MAIL_DIR}
+ case $config in
+ traditional)
+ HOOK_DIR=${MAIL_DIR}/.notmuch/hooks
+ ;;
+ profile)
+ dir=${HOME}/.config/notmuch/other
+ mkdir -p ${dir}
+ HOOK_DIR=${dir}/hooks
+ cp ${NOTMUCH_CONFIG} ${dir}/config
+ export NOTMUCH_PROFILE=other
+ ;;
+ explicit)
+ HOOK_DIR=${HOME}/.notmuch-hooks
+ mkdir -p $HOOK_DIR
+ notmuch config set database.hook_dir $HOOK_DIR
+ ;;
+ relative)
+ HOOK_DIR=${HOME}/.notmuch-hooks
+ mkdir -p $HOOK_DIR
+ notmuch config set database.hook_dir .notmuch-hooks
+ ;;
+ XDG)
+ HOOK_DIR=${HOME}/.config/notmuch/default/hooks
+ ;;
+ split)
+ dir="$TMP_DIRECTORY/database.$test_count"
+ notmuch config set database.path $dir
+ notmuch config set database.mail_root $MAIL_DIR
+ HOOK_DIR=${dir}/hooks
+ ;;
+ esac
+
+ test_begin_subtest "pre-new is run [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message
+ create_echo_hook "pre-new" expected output $HOOK_DIR
+ notmuch new > /dev/null
+ test_expect_equal_file expected output
+
+ test_begin_subtest "post-new is run [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message
+ create_echo_hook "post-new" expected output $HOOK_DIR
+ notmuch new > /dev/null
+ test_expect_equal_file expected output
+
+ test_begin_subtest "post-insert hook is run [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message
+ create_echo_hook "post-insert" expected output $HOOK_DIR
+ notmuch insert < "$gen_msg_filename"
+ test_expect_equal_file expected output
+
+ test_begin_subtest "pre-new is run before post-new [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message
+ create_echo_hook "pre-new" pre-new.expected pre-new.output $HOOK_DIR
+ create_echo_hook "post-new" post-new.expected post-new.output $HOOK_DIR
+ notmuch new > /dev/null
+ test_expect_equal_file post-new.expected post-new.output
+
+ test_begin_subtest "pre-new non-zero exit status (hook status) [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message
+ create_failing_hook "pre-new" $HOOK_DIR
+ output=`notmuch new 2>&1`
+ test_expect_equal "$output" "Error: pre-new hook failed with status 13"
+
+ # depends on the previous subtest leaving broken hook behind
+ test_begin_subtest "pre-new non-zero exit status (notmuch status) [${config}]"
+ test_expect_code 1 "notmuch new"
+
+ # depends on the previous subtests leaving 1 new message behind
+ test_begin_subtest "pre-new non-zero exit status aborts new [${config}]"
+ rm -rf ${HOOK_DIR}
+ output=$(NOTMUCH_NEW)
+ test_expect_equal "$output" "Added 1 new message to the database."
+
+ test_begin_subtest "post-new non-zero exit status (hook status) [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message
+ create_failing_hook "post-new" $HOOK_DIR
+ NOTMUCH_NEW 2>output.stderr >output
+ cat output.stderr >> output
+ echo "Added 1 new message to the database." > expected
+ echo "Error: post-new hook failed with status 13" >> expected
+ test_expect_equal_file expected output
+
+ # depends on the previous subtest leaving broken hook behind
+ test_begin_subtest "post-new non-zero exit status (notmuch status) [${config}]"
+ test_expect_code 1 "notmuch new"
+
+ test_begin_subtest "post-insert hook does not affect insert status [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message
+ create_failing_hook "post-insert" $HOOK_DIR
+ test_expect_success "notmuch insert < \"$gen_msg_filename\" > /dev/null"
+
+ test_begin_subtest "hook without executable permissions [${config}]"
+ rm -rf ${HOOK_DIR}
+ mkdir -p ${HOOK_DIR}
+ cat <<EOF >"${HOOK_DIR}/pre-new"
+ #!/bin/sh
+ echo foo
+EOF
+ output=`notmuch new 2>&1`
+ test_expect_code 1 "notmuch new"
+
+ test_begin_subtest "hook execution failure [${config}]"
+ rm -rf ${HOOK_DIR}
+ mkdir -p ${HOOK_DIR}
+ cat <<EOF >"${HOOK_DIR}/pre-new"
+ no hashbang, execl fails
EOF
-output=`notmuch new 2>&1`
-test_expect_code 1 "notmuch new"
-
-test_begin_subtest "hook execution failure"
-rm_hooks
-mkdir -p ${HOOK_DIR}
-cat <<EOF >"${HOOK_DIR}/pre-new"
-no hashbang, execl fails
+ chmod +x "${HOOK_DIR}/pre-new"
+ test_expect_code 1 "notmuch new"
+
+ test_begin_subtest "post-new with write access [${config}]"
+ rm -rf ${HOOK_DIR}
+ create_write_hook "post-new" write.expected write.output $HOOK_DIR
+ NOTMUCH_NEW
+ test_expect_equal_file write.expected write.output
+
+ test_begin_subtest "pre-new with write access [${config}]"
+ rm -rf ${HOOK_DIR}
+ create_write_hook "pre-new" write.expected write.output $HOOK_DIR
+ NOTMUCH_NEW
+ test_expect_equal_file write.expected write.output
+
+ test_begin_subtest "add message in pre-new [${config}]"
+ rm -rf ${HOOK_DIR}
+ generate_message '[subject]="add msg in pre-new"'
+ id1=$gen_msg_id
+ create_change_hook "pre-new" $gen_msg_filename $HOOK_DIR
+ generate_message '[subject]="add msg in new"'
+ NOTMUCH_NEW
+ notmuch search id:$id1 or id:$gen_msg_id | notmuch_search_sanitize > OUTPUT
+ cat <<EOF | sed s'/^[ \t]*//' > EXPECTED
+ thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; add msg in pre-new (inbox unread)
+ thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; add msg in new (inbox unread)
EOF
-chmod +x "${HOOK_DIR}/pre-new"
-test_expect_code 1 "notmuch new"
+ test_expect_equal_file EXPECTED OUTPUT
+ rm -rf ${HOOK_DIR}
+done
test_done
. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "sanity check"
-$TEST_DIRECTORY/arg-test pos1 --keyword=one --boolean --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
+$TEST_DIRECTORY/arg-test pos1 --keyword=one --boolean --string=foo pos2 --int=7 --flag=one --flag=three > OUTPUT
cat <<EOF > EXPECTED
boolean 1
keyword 1
test_begin_subtest "round trip newlines"
printf 'this\n tag\t has\n spaces\n' > EXPECTED.$test_count
-$TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED.$test_count |\
+$TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED.$test_count |\
$TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT.$test_count
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
test_begin_subtest "round trip 8bit chars"
echo '%c3%91%c3%a5%c3%b0%c3%a3%c3%a5%c3%a9-%c3%8f%c3%8a' > EXPECTED.$test_count
-$TEST_DIRECTORY/hex-xcode --direction=decode < EXPECTED.$test_count |\
+$TEST_DIRECTORY/hex-xcode --direction=decode < EXPECTED.$test_count |\
$TEST_DIRECTORY/hex-xcode --direction=encode > OUTPUT.$test_count
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
test_begin_subtest "round trip newlines (in-place)"
printf 'this\n tag\t has\n spaces\n' > EXPECTED.$test_count
-$TEST_DIRECTORY/hex-xcode --in-place --direction=encode < EXPECTED.$test_count |\
+$TEST_DIRECTORY/hex-xcode --in-place --direction=encode < EXPECTED.$test_count |\
$TEST_DIRECTORY/hex-xcode --in-place --direction=decode > OUTPUT.$test_count
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
test_begin_subtest "round trip 8bit chars (in-place)"
echo '%c3%91%c3%a5%c3%b0%c3%a3%c3%a5%c3%a9-%c3%8f%c3%8a' > EXPECTED.$test_count
-$TEST_DIRECTORY/hex-xcode --in-place --direction=decode < EXPECTED.$test_count |\
+$TEST_DIRECTORY/hex-xcode --in-place --direction=decode < EXPECTED.$test_count |\
$TEST_DIRECTORY/hex-xcode --in-place --direction=encode > OUTPUT.$test_count
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
add_email_corpus threading
test_begin_subtest "reply to ghost"
-notmuch show --entire-thread=true id:000-real-root@example.org | grep ^Subject: | head -1 > OUTPUT
+notmuch show --entire-thread=true id:000-real-root@example.org | grep ^Subject: | head -1 > OUTPUT
cat <<EOF > EXPECTED
Subject: root message
EOF
--- /dev/null
+#!/usr/bin/env bash
+test_description='database upgrades'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+test_require_external_prereq xapian-metadata
+
+XAPIAN_PATH=$MAIL_DIR/.notmuch/xapian
+BACKUP_PATH=$MAIL_DIR/.notmuch/backups
+
+delete_feature () {
+ local key=$1
+ features=$(xapian-metadata get $XAPIAN_PATH features | grep -v "^$key")
+ xapian-metadata set $XAPIAN_PATH features "$features"
+}
+
+add_email_corpus
+
+for key in 'multiple paths per message' \
+ 'relative directory paths' \
+ 'exact folder:/path: search' \
+ 'mail documents for missing messages' \
+ 'modification tracking'; do
+ backup_database
+ test_begin_subtest "upgrade is triggered by missing '$key'"
+ delete_feature "$key"
+ output=$(notmuch new | grep Welcome)
+ test_expect_equal \
+ "$output" \
+ "Welcome to a new version of notmuch! Your database will now be upgraded."
+
+ restore_database
+
+ backup_database
+ test_begin_subtest "backup can be restored ['$key']"
+ notmuch dump > BEFORE
+ delete_feature "$key"
+ notmuch new
+ notmuch tag -inbox '*'
+ dump_file=$(echo ${BACKUP_PATH}/dump*)
+ notmuch restore --input=$dump_file
+ notmuch dump > AFTER
+ test_expect_equal_file BEFORE AFTER
+ restore_database
+done
+
+for key in 'from/subject/message-ID in database' \
+ 'indexed MIME types' \
+ 'index body and headers separately'; do
+ backup_database
+ test_begin_subtest "upgrade not triggered by missing '$key'"
+ delete_feature "$key"
+ output=$(notmuch new | grep Welcome)
+ test_expect_equal "$output" ""
+ restore_database
+done
+
+test_begin_subtest "upgrade with configured backup dir"
+notmuch config set database.backup_dir ${HOME}/backups
+delete_feature 'modification tracking'
+notmuch new | grep Backing | notmuch_dir_sanitize | sed 's/dump-[0-9T]*/dump-XXX/' > OUTPUT
+cat <<EOF > EXPECTED
+Backing up tags to CWD/home/backups/dump-XXX.gz...
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "upgrade with relative configured backup dir"
+notmuch config set database.backup_dir ${HOME}/backups
+delete_feature 'modification tracking'
+notmuch new | grep Backing | notmuch_dir_sanitize | sed 's/dump-[0-9T]*/dump-XXX/' > OUTPUT
+cat <<EOF > EXPECTED
+Backing up tags to CWD/home/backups/dump-XXX.gz...
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_done
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
-Error: Cannot open a database for a NULL path.
+Error: could not locate database.
EOF
test_expect_equal_file EXPECTED OUTPUT
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
-Error opening database at CWD/nonexistent/foo/.notmuch: No such file or directory
+Error: Cannot open database at CWD/nonexistent/foo: No such file or directory.
EOF
test_expect_equal_file EXPECTED OUTPUT
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
-Error: Cannot create a database for a NULL path.
+Error: could not locate database.
EOF
test_expect_equal_file EXPECTED OUTPUT
cat <<'EOF' >EXPECTED
== stdout ==
== stderr ==
-Error: Cannot create database at CWD/nonexistent/foo: No such file or directory.
+Error: Cannot open database at CWD/nonexistent/foo: No such file or directory.
EOF
test_expect_equal_file EXPECTED OUTPUT
notmuch_tags_t *result;
EXPECT0(notmuch_database_close (db));
result = notmuch_database_get_all_tags (db);
- printf("%d\n", result == NULL);
+ printf("%d\n", result == NULL);
stat = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
EOF
test_begin_subtest "get config from closed database"
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
{
- const char *result;
+ char *result;
EXPECT0(notmuch_database_close (db));
stat = notmuch_database_get_config (db, "foo", &result);
- printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+ printf("%d\n", stat == NOTMUCH_STATUS_SUCCESS);
}
EOF
cat <<EOF > EXPECTED
== stdout ==
1
== stderr ==
-Error: A Xapian exception occurred getting metadata: Database has been closed
EOF
test_expect_equal_file EXPECTED OUTPUT
{
EXPECT0(notmuch_database_close (db));
stat = notmuch_database_set_config (db, "foo", "bar");
- printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+ printf("%d\n", stat == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
}
EOF
cat <<EOF > EXPECTED
notmuch_indexopts_t *result;
EXPECT0(notmuch_database_close (db));
result = notmuch_database_get_default_indexopts (db);
- printf("%d\n", result == NULL);
+ printf("%d\n", result != NULL);
}
EOF
cat <<EOF > EXPECTED
EXPECT0(notmuch_database_close (db));
notmuch_decryption_policy_t policy = notmuch_indexopts_get_decrypt_policy (result);
stat = notmuch_indexopts_set_decrypt_policy (result, policy);
- printf("%d\n%d\n", policy == NOTMUCH_DECRYPT_AUTO, stat == NOTMUCH_STATUS_SUCCESS);
+ printf("%d\n%d\n", policy == NOTMUCH_DECRYPT_AUTO, stat == NOTMUCH_STATUS_SUCCESS);
}
EOF
cat <<EOF > EXPECTED
notmuch_status_t status;
notmuch_bool_t out;
status = notmuch_message_has_maildir_flag_st (message, 'S', &out);
- printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+ printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
}
EOF
cat <<EOF > EXPECTED
{
notmuch_status_t status;
status = notmuch_message_maildir_flags_to_tags (message);
- printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+ printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
}
EOF
cat <<EOF > EXPECTED
{
notmuch_status_t status;
status = notmuch_message_remove_all_tags (message);
- printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+ printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
}
EOF
cat <<EOF > EXPECTED
{
notmuch_status_t status;
status = notmuch_message_freeze (message);
- printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_SUCCESS);
+ printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_SUCCESS);
}
EOF
cat <<EOF > EXPECTED
{
notmuch_status_t status;
status = notmuch_message_thaw (message);
- printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW);
+ printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_UNBALANCED_FREEZE_THAW);
}
EOF
cat <<EOF > EXPECTED
cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
{
notmuch_message_destroy (message);
- printf("%d\n%d\n", message != NULL, 1);
+ printf("%d\n%d\n", message != NULL, 1);
}
EOF
cat <<EOF > EXPECTED
{
notmuch_database_t *db2;
db2 = notmuch_message_get_database (message);
- printf("%d\n%d\n", message != NULL, db == db2);
+ printf("%d\n%d\n", message != NULL, db == db2);
}
EOF
cat <<EOF > EXPECTED
{
notmuch_status_t status;
status = notmuch_message_reindex (message, NULL);
- printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
+ printf("%d\n%d\n", message != NULL, status == NOTMUCH_STATUS_XAPIAN_EXCEPTION);
}
EOF
cat <<EOF > EXPECTED
notmuch_database_t *db;
char *val;
notmuch_status_t stat;
+ char *msg = NULL;
- EXPECT0(notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db));
+ for (int i = 1; i < argc; i++)
+ if (strcmp (argv[i], "%NULL%") == 0) argv[i] = NULL;
+ stat = notmuch_database_open_with_config (argv[1],
+ NOTMUCH_DATABASE_MODE_READ_WRITE,
+ argv[2],
+ argv[3],
+ &db,
+ &msg);
+ if (stat != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "error opening database\n%s\n%s\n", notmuch_status_to_string (stat), msg ? msg : "");
+ exit (1);
+ }
EOF
cat <<EOF > c_tail
EOF
test_begin_subtest "notmuch_database_{set,get}_config"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG}
{
- EXPECT0(notmuch_database_set_config (db, "testkey1", "testvalue1"));
- EXPECT0(notmuch_database_set_config (db, "testkey2", "testvalue2"));
- EXPECT0(notmuch_database_get_config (db, "testkey1", &val));
- printf("testkey1 = %s\n", val);
- EXPECT0(notmuch_database_get_config (db, "testkey2", &val));
- printf("testkey2 = %s\n", val);
+ EXPECT0(notmuch_database_set_config (db, "test.key1", "testvalue1"));
+ EXPECT0(notmuch_database_set_config (db, "test.key2", "testvalue2"));
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
}
EOF
cat <<'EOF' >EXPECTED
== stdout ==
-testkey1 = testvalue1
-testkey2 = testvalue2
+test.key1 = testvalue1
+test.key2 = testvalue2
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "notmuch_database_get_config_list: empty list"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
{
notmuch_config_list_t *list;
EXPECT0(notmuch_database_get_config_list (db, "nonexistent", &list));
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "notmuch_database_get_config_list: all pairs"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
{
notmuch_config_list_t *list;
EXPECT0(notmuch_database_set_config (db, "zzzafter", "afterval"));
cat <<'EOF' >EXPECTED
== stdout ==
aaabefore beforeval
-testkey1 testvalue1
-testkey2 testvalue2
+test.key1 testvalue1
+test.key2 testvalue2
zzzafter afterval
== stderr ==
EOF
cat <<'EOF' >EXPECTED
== stdout ==
aaabefore 1
-testkey1 1
-testkey2 1
+test.key1 1
+test.key2 1
zzzafter 1
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "notmuch_database_get_config_list: one prefix"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
{
notmuch_config_list_t *list;
- EXPECT0(notmuch_database_get_config_list (db, "testkey", &list));
+ EXPECT0(notmuch_database_get_config_list (db, "test.key", &list));
for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
}
EOF
cat <<'EOF' >EXPECTED
== stdout ==
-testkey1 testvalue1
-testkey2 testvalue2
+test.key1 testvalue1
+test.key2 testvalue2
== stderr ==
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "dump config"
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
{
EXPECT0(notmuch_database_set_config (db, "key with spaces", "value, with, spaces!"));
}
#notmuch-dump batch-tag:3 config
#@ aaabefore beforeval
#@ key%20with%20spaces value,%20with,%20spaces%21
-#@ testkey1 testvalue1
-#@ testkey2 testvalue2
+#@ test.key1 testvalue1
+#@ test.key2 testvalue2
#@ zzzafter afterval
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "restore config"
notmuch dump --include=config >EXPECTED
-cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
{
- EXPECT0(notmuch_database_set_config (db, "testkey1", "mutatedvalue"));
+ EXPECT0(notmuch_database_set_config (db, "test.key1", "mutatedvalue"));
}
EOF
notmuch restore --include=config <EXPECTED
notmuch dump --include=config >OUTPUT
test_expect_equal_file EXPECTED OUTPUT
+backup_database
+test_begin_subtest "override config from file"
+notmuch config set test.key1 overridden
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG}
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = overridden
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+test_begin_subtest "NOTMUCH_CONFIG_HOOK_DIR: traditional"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ const char *val = notmuch_config_get (db, NOTMUCH_CONFIG_HOOK_DIR);
+ printf("database.hook_dir = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+database.hook_dir = MAIL_DIR/.notmuch/hooks
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "NOTMUCH_CONFIG_HOOK_DIR: xdg"
+dir="${HOME}/.config/notmuch/default/hooks"
+mkdir -p $dir
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ const char *val = notmuch_config_get (db, NOTMUCH_CONFIG_HOOK_DIR);
+ printf("database.hook_dir = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+database.hook_dir = CWD/home/.config/notmuch/default/hooks
+== stderr ==
+EOF
+rmdir $dir
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_config_get_values"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_values_t *values;
+ EXPECT0(notmuch_config_set (db, NOTMUCH_CONFIG_NEW_TAGS, "a;b;c"));
+ for (values = notmuch_config_get_values (db, NOTMUCH_CONFIG_NEW_TAGS);
+ notmuch_config_values_valid (values);
+ notmuch_config_values_move_to_next (values))
+ {
+ puts (notmuch_config_values_get (values));
+ }
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+a
+b
+c
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+test_begin_subtest "notmuch_config_get_values_string"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_values_t *values;
+ EXPECT0(notmuch_database_set_config (db, "test.list", "x;y;z"));
+ for (values = notmuch_config_get_values_string (db, "test.list");
+ notmuch_config_values_valid (values);
+ notmuch_config_values_move_to_next (values))
+ {
+ puts (notmuch_config_values_get (values));
+ }
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+x
+y
+z
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+test_begin_subtest "notmuch_config_get_values (restart)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_values_t *values;
+ EXPECT0(notmuch_config_set (db, NOTMUCH_CONFIG_NEW_TAGS, "a;b;c"));
+ for (values = notmuch_config_get_values (db, NOTMUCH_CONFIG_NEW_TAGS);
+ notmuch_config_values_valid (values);
+ notmuch_config_values_move_to_next (values))
+ {
+ puts (notmuch_config_values_get (values));
+ }
+ for (notmuch_config_values_start (values);
+ notmuch_config_values_valid (values);
+ notmuch_config_values_move_to_next (values))
+ {
+ puts (notmuch_config_values_get (values));
+ }
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+a
+b
+c
+a
+b
+c
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "notmuch_config_get_values, trailing ;"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_values_t *values;
+ EXPECT0(notmuch_config_set (db, NOTMUCH_CONFIG_NEW_TAGS, "a;b;c"));
+ for (values = notmuch_config_get_values (db, NOTMUCH_CONFIG_NEW_TAGS);
+ notmuch_config_values_valid (values);
+ notmuch_config_values_move_to_next (values))
+ {
+ puts (notmuch_config_values_get (values));
+ }
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+a
+b
+c
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "get config by key"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG}
+{
+ printf("before = %s\n", notmuch_config_get (db, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS));
+ EXPECT0(notmuch_database_set_config (db, "maildir.synchronize_flags", "false"));
+ printf("after = %s\n", notmuch_config_get (db, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS));
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+before = true
+after = false
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "set config by key"
+notmuch config set test.key1 overridden
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG}
+{
+ printf("before = %s\n", notmuch_config_get (db, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS));
+ EXPECT0(notmuch_config_set (db, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS, "false"));
+ printf("after = %s\n", notmuch_config_get (db, NOTMUCH_CONFIG_SYNC_MAILDIR_FLAGS));
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+before = true
+after = false
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+test_begin_subtest "load default values"
+export MAILDIR=${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} '' %NULL%
+{
+ notmuch_config_key_t key;
+ for (key = NOTMUCH_CONFIG_FIRST;
+ key < NOTMUCH_CONFIG_LAST;
+ key = (notmuch_config_key_t)(key + 1)) {
+ const char *val = notmuch_config_get (db, key);
+ printf("%s\n", val ? val : "NULL" );
+ }
+}
+EOF
+
+notmuch_passwd_sanitize < OUTPUT > OUTPUT.clean
+
+cat <<'EOF' >EXPECTED
+== stdout ==
+MAIL_DIR
+MAIL_DIR
+MAIL_DIR/.notmuch/hooks
+MAIL_DIR/.notmuch/backups
+
+unread;inbox
+
+true
+USERNAME@FQDN
+NULL
+USER_FULL_NAME
+== stderr ==
+EOF
+unset MAILDIR
+test_expect_equal_file EXPECTED OUTPUT.clean
+
+backup_database
+test_begin_subtest "override config from \${NOTMUCH_CONFIG}"
+notmuch config set test.key1 overridden
+# second argument omitted to make argv[2] == NULL
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+notmuch config set test.key1
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = overridden
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "override config from \${HOME}/.notmuch-config"
+ovconfig=${HOME}/.notmuch-config
+cp ${NOTMUCH_CONFIG} ${ovconfig}
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+unset NOTMUCH_CONFIG
+notmuch --config=${ovconfig} config set test.key1 overridden-home
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+rm -f ${ovconfig}
+NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = overridden-home
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "override config from \${XDG_CONFIG_HOME}/notmuch"
+ovconfig=${HOME}/.config/notmuch/default/config
+mkdir -p $(dirname ${ovconfig})
+cp ${NOTMUCH_CONFIG} ${ovconfig}
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+unset NOTMUCH_CONFIG
+notmuch --config=${ovconfig} config set test.key1 overridden-xdg
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+rm -f ${ovconfig}
+NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = overridden-xdg
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "override config from \${XDG_CONFIG_HOME}/notmuch with profile"
+ovconfig=${HOME}/.config/notmuch/work/config
+mkdir -p $(dirname ${ovconfig})
+cp ${NOTMUCH_CONFIG} ${ovconfig}
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+unset NOTMUCH_CONFIG
+notmuch --config=${ovconfig} config set test.key1 overridden-xdg-profile
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% work
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+rm -f ${ovconfig}
+NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = overridden-xdg-profile
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+backup_database
+test_begin_subtest "override config from \${HOME}/.notmuch-config.work (via args)"
+ovconfig=${HOME}/.notmuch-config.work
+cp ${NOTMUCH_CONFIG} ${ovconfig}
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+unset NOTMUCH_CONFIG
+notmuch --config=${ovconfig} config set test.key1 overridden-profile
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% work
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+#rm -f ${ovconfig}
+NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = overridden-profile
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+test_begin_subtest "no config, fail to open database"
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+unset NOTMUCH_CONFIG
+cat c_head - c_tail <<'EOF' | test_C %NULL% '' %NULL%
+{
+ printf("NOT RUN");
+}
+EOF
+NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<'EOF' >EXPECTED
+== stdout ==
+== stderr ==
+error opening database
+No database found
+Error: could not locate database.
+
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "open database from NOTMUCH_DATABASE"
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+unset NOTMUCH_CONFIG
+export NOTMUCH_DATABASE=${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C %NULL% '' %NULL%
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+export NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+unset NOTMUCH_DATABASE
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = testvalue1
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "NOTMUCH_DATABASE overrides config"
+cp notmuch-config notmuch-config.bak
+notmuch config set database.path /nonexistent
+export NOTMUCH_DATABASE=${MAIL_DIR}
+cat c_head - c_tail <<'EOF' | test_C %NULL% '' %NULL%
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+unset NOTMUCH_DATABASE
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = testvalue1
+test.key2 = testvalue2
+== stderr ==
+EOF
+cp notmuch-config.bak notmuch-config
+test_expect_equal_file EXPECTED OUTPUT
+
+cat <<EOF > c_head2
+#include <string.h>
+#include <stdlib.h>
+#include <notmuch-test.h>
+
+int main (int argc, char** argv)
+{
+ notmuch_database_t *db;
+ char *val;
+ notmuch_status_t stat;
+ char *msg = NULL;
+
+ for (int i = 1; i < argc; i++)
+ if (strcmp (argv[i], "%NULL%") == 0) argv[i] = NULL;
+
+ stat = notmuch_database_load_config (argv[1],
+ argv[2],
+ argv[3],
+ &db,
+ &msg);
+ if (stat != NOTMUCH_STATUS_SUCCESS && stat != NOTMUCH_STATUS_NO_CONFIG) {
+ fprintf (stderr, "error opening database\n%d: %s\n%s\n", stat,
+ notmuch_status_to_string (stat), msg ? msg : "");
+ exit (1);
+ }
+EOF
+
+
+test_begin_subtest "notmuch_database_get_config (ndlc)"
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = testvalue1
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+
+test_begin_subtest "notmuch_database_get_config_list: all pairs (ndlc)"
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_list_t *list;
+ EXPECT0(notmuch_database_get_config_list (db, "", &list));
+ for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+ printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+ }
+ notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+aaabefore beforeval
+key with spaces value, with, spaces!
+test.key1 testvalue1
+test.key2 testvalue2
+zzzafter afterval
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_database_get_config_list: one prefix (ndlc)"
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_list_t *list;
+ EXPECT0(notmuch_database_get_config_list (db, "test.key", &list));
+ for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+ printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
+ }
+ notmuch_config_list_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 testvalue1
+test.key2 testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "list by keys (ndlc)"
+notmuch config set search.exclude_tags "foo;bar;fub"
+notmuch config set new.ignore "sekrit_junk"
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
+{
+ notmuch_config_key_t key;
+ for (key = NOTMUCH_CONFIG_FIRST;
+ key < NOTMUCH_CONFIG_LAST;
+ key = (notmuch_config_key_t)(key + 1)) {
+ const char *val = notmuch_config_get (db, key);
+ printf("%s\n", val ? val : "NULL" );
+ }
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+MAIL_DIR
+MAIL_DIR
+MAIL_DIR/.notmuch/hooks
+MAIL_DIR/.notmuch/backups
+foo;bar;fub
+unread;inbox
+sekrit_junk
+true
+test_suite@notmuchmail.org
+test_suite_other@notmuchmail.org;test_suite@otherdomain.org
+Notmuch Test Suite
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "load default values (ndlc, nonexistent config)"
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} /nonexistent %NULL%
+{
+ notmuch_config_key_t key;
+ for (key = NOTMUCH_CONFIG_FIRST;
+ key < NOTMUCH_CONFIG_LAST;
+ key = (notmuch_config_key_t)(key + 1)) {
+ const char *val = notmuch_config_get (db, key);
+ printf("%s\n", val ? val : "NULL" );
+ }
+}
+EOF
+
+notmuch_passwd_sanitize < OUTPUT > OUTPUT.clean
+cat <<'EOF' >EXPECTED
+== stdout ==
+MAIL_DIR
+MAIL_DIR
+MAIL_DIR/.notmuch/hooks
+MAIL_DIR/.notmuch/backups
+
+unread;inbox
+
+true
+USERNAME@FQDN
+NULL
+USER_FULL_NAME
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT.clean
+
+backup_database
+test_begin_subtest "override config from \${HOME}/.notmuch-config (ndlc)"
+ovconfig=${HOME}/.notmuch-config
+cp ${NOTMUCH_CONFIG} ${ovconfig}
+old_NOTMUCH_CONFIG=${NOTMUCH_CONFIG}
+unset NOTMUCH_CONFIG
+notmuch --config=${ovconfig} config set test.key1 overridden-home
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} %NULL% %NULL%
+{
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+rm -f ${ovconfig}
+NOTMUCH_CONFIG=${old_NOTMUCH_CONFIG}
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = overridden-home
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+restore_database
+
+test_begin_subtest "notmuch_config_get_pairs: prefix (ndlc)"
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_pairs_t *list;
+ for (list = notmuch_config_get_pairs (db, "user.");
+ notmuch_config_pairs_valid (list);
+ notmuch_config_pairs_move_to_next (list)) {
+ printf("%s %s\n", notmuch_config_pairs_key (list), notmuch_config_pairs_value(list));
+ }
+ notmuch_config_pairs_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+user.name Notmuch Test Suite
+user.other_email test_suite_other@notmuchmail.org;test_suite@otherdomain.org
+user.primary_email test_suite@notmuchmail.org
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_config_get_pairs: all pairs (ndlc)"
+cat c_head2 - c_tail <<'EOF' | test_C ${MAIL_DIR} ${NOTMUCH_CONFIG} %NULL%
+{
+ notmuch_config_pairs_t *list;
+ for (list = notmuch_config_get_pairs (db, "");
+ notmuch_config_pairs_valid (list);
+ notmuch_config_pairs_move_to_next (list)) {
+ printf("%s %s\n", notmuch_config_pairs_key (list), notmuch_config_pairs_value(list));
+ }
+ notmuch_config_pairs_destroy (list);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+aaabefore beforeval
+database.backup_dir MAIL_DIR/.notmuch/backups
+database.hook_dir MAIL_DIR/.notmuch/hooks
+database.mail_root MAIL_DIR
+database.path MAIL_DIR
+key with spaces value, with, spaces!
+maildir.synchronize_flags true
+new.ignore sekrit_junk
+new.tags unread;inbox
+search.exclude_tags foo;bar;fub
+test.key1 testvalue1
+test.key2 testvalue2
+user.name Notmuch Test Suite
+user.other_email test_suite_other@notmuchmail.org;test_suite@otherdomain.org
+user.primary_email test_suite@notmuchmail.org
+zzzafter afterval
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_done
--- /dev/null
+#!/usr/bin/env bash
+test_description="library reopen API"
+
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+cat <<EOF > c_head
+#include <string.h>
+#include <stdlib.h>
+#include <notmuch-test.h>
+
+int main (int argc, char** argv)
+{
+ notmuch_database_t *db;
+ char *val;
+ notmuch_status_t stat;
+ notmuch_database_mode_t mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
+
+ char *msg = NULL;
+
+ for (int i = 1; i < argc; i++)
+ if (strcmp (argv[i], "%NULL%") == 0) argv[i] = NULL;
+
+ if (argv[2] && (argv[2][0] == 'w' || argv[2][0] == 'W'))
+ mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
+
+ stat = notmuch_database_open_with_config (argv[1],
+ mode,
+ argv[3],
+ argv[4],
+ &db,
+ &msg);
+ if (stat != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "error opening database: %d %s\n", stat, msg ? msg : "");
+ exit (1);
+ }
+EOF
+
+cat <<EOF > c_tail
+ EXPECT0(notmuch_database_destroy(db));
+}
+EOF
+
+# The sequence of tests is important here
+
+test_begin_subtest "notmuch_database_reopen (read=>write)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} read ${NOTMUCH_CONFIG}
+{
+ EXPECT0(notmuch_database_reopen (db, NOTMUCH_DATABASE_MODE_READ_WRITE));
+ EXPECT0(notmuch_database_set_config (db, "test.key1", "testvalue1"));
+ EXPECT0(notmuch_database_set_config (db, "test.key2", "testvalue2"));
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = testvalue1
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_database_reopen (read=>read)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} read ${NOTMUCH_CONFIG}
+{
+ EXPECT0(notmuch_database_reopen (db, NOTMUCH_DATABASE_MODE_READ_ONLY));
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = testvalue1
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_database_reopen (write=>read)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} write ${NOTMUCH_CONFIG}
+{
+ EXPECT0(notmuch_database_reopen (db, NOTMUCH_DATABASE_MODE_READ_ONLY));
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = testvalue1
+test.key2 = testvalue2
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "notmuch_database_reopen (write=>write)"
+cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR} write ${NOTMUCH_CONFIG}
+{
+ EXPECT0(notmuch_database_reopen (db, NOTMUCH_DATABASE_MODE_READ_WRITE));
+ EXPECT0(notmuch_database_set_config (db, "test.key3", "testvalue3"));
+ EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+ printf("test.key1 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+ printf("test.key2 = %s\n", val);
+ EXPECT0(notmuch_database_get_config (db, "test.key3", &val));
+ printf("test.key3 = %s\n", val);
+}
+EOF
+cat <<'EOF' >EXPECTED
+== stdout ==
+test.key1 = testvalue1
+test.key2 = testvalue2
+test.key3 = testvalue3
+== stderr ==
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
QUERYSTR="date:2009-11-18..2009-11-18 and tag:unread"
-test_begin_subtest "error adding named query before initializing DB"
-test_expect_code 1 "notmuch config set query.test \"$QUERYSTR\""
+test_begin_subtest "error adding named query to DB before initialization"
+test_expect_code 1 "notmuch config set --database query.test \"$QUERYSTR\""
add_email_corpus
-test_begin_subtest "adding named query"
-test_expect_success "notmuch config set query.test \"$QUERYSTR\""
+test_begin_subtest "adding named query (database)"
+test_expect_success "notmuch config set --database query.test \"$QUERYSTR\""
test_begin_subtest "adding nested named query"
QUERYSTR2="query:test and subject:Maildir"
notmuch dump | grep '^#@' > OUTPUT
cat<<EOF > QUERIES.BEFORE
#@ query.test date%3a2009-11-18..2009-11-18%20and%20tag%3aunread
-#@ query.test2 query%3atest%20and%20subject%3aMaildir
EOF
test_expect_equal_file QUERIES.BEFORE OUTPUT
# This value is just large enough to trigger a limitation of gzprintf
# to 8191 bytes in total (by default).
repeat=1329
-notmuch config set query.big "$(seq -s' ' $repeat)"
+notmuch config set --database query.big "$(seq -s' ' $repeat)"
notmuch dump --include=config > OUTPUT
-notmuch config set query.big ''
+notmuch config set --database query.big
printf "#notmuch-dump batch-tag:3 config\n#@ query.big " > EXPECTED
seq -s'%20' $repeat >> EXPECTED
cat <<EOF >> EXPECTED
#@ query.test date%3a2009-11-18..2009-11-18%20and%20tag%3aunread
-#@ query.test2 query%3atest%20and%20subject%3aMaildir
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "delete named queries"
notmuch dump > BEFORE
-notmuch config set query.test
+notmuch config set --database query.test
notmuch dump | grep '^#@' > OUTPUT
cat<<EOF > EXPECTED
-#@ query.test2 query%3atest%20and%20subject%3aMaildir
EOF
test_expect_equal_file EXPECTED OUTPUT
test_description="duplicate message ids"
. $(dirname "$0")/test-lib.sh || exit 1
+test_require_external_prereq xapian-delve
+
add_message '[id]="duplicate"' '[subject]="message 1" [filename]=copy1'
add_message '[id]="duplicate"' '[subject]="message 2" [filename]=copy2'
test_expect_equal_file initial-dump OUTPUT
test_begin_subtest 'reindex preserves tags with special prefixes'
-notmuch tag +attachment2 +encrypted2 +signed2 '*'
+notmuch tag +attachment2 +encrypted2 +signed2 '*'
notmuch dump > EXPECTED
notmuch reindex '*'
notmuch dump > OUTPUT
-notmuch tag -attachment2 -encrypted2 -signed2 '*'
+notmuch tag -attachment2 -encrypted2 -signed2 '*'
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest 'reindex moves a message between threads'
add_email_corpus lkml
test_begin_subtest "new doesn't run out of file descriptors with many gzipped files"
ulimit -n 200
-gzip --recursive ${MAIL_DIR}
+find ${MAIL_DIR} -name .notmuch -prune -o -type f -print0 | xargs -0 gzip --
test_expect_success "notmuch new"
test_done
test_description='indexing user specified headers'
. $(dirname "$0")/test-lib.sh || exit 1
-test_begin_subtest "error adding user header before initializing DB"
-notmuch config set index.header.List List-Id 2>&1 | notmuch_dir_sanitize > OUTPUT
-cat <<EOF > EXPECTED
-Error opening database at MAIL_DIR/.notmuch: No such file or directory
-EOF
-test_expect_equal_file EXPECTED OUTPUT
-
add_email_corpus
notmuch search '*' | notmuch_search_sanitize > initial-threads
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "index user header, config from file"
+field_name="Test"
+printf "\n[index]\nheader.${field_name} = List-Id\n" >> notmuch-config
+notmuch reindex '*'
+notmuch search --output=files ${field_name}:notmuch | notmuch_search_files_sanitize | sort > OUTPUT
+cat <<EOF > EXPECTED
+MAIL_DIR/bar/baz/05:2,
+MAIL_DIR/bar/baz/23:2,
+MAIL_DIR/bar/baz/24:2,
+MAIL_DIR/bar/cur/20:2,
+MAIL_DIR/bar/new/21:2,
+MAIL_DIR/bar/new/22:2,
+MAIL_DIR/foo/cur/08:2,
+MAIL_DIR/foo/new/03:2,
+MAIL_DIR/new/04:2,
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_done
}
Xapian::Database db (argv[1]);
+
std::cout << db.get_termfreq ("Tghost") << std::endl;
}
-#!/usr/bin/env python
+#!/usr/bin/env python3
import re
import sys
import json
}
std::string nmpath (argv[1]);
+
nmpath += "/.notmuch";
if (mkdir (nmpath.c_str (), 0777) < 0) {
perror (("failed to create " + nmpath).c_str ());
# Run the tests
if test -z "$NOTMUCH_TEST_SERIALIZE" && command -v parallel >/dev/null ; then
test -t 1 && export COLORS_WITHOUT_TTY=t || :
- if parallel -h | grep -q GNU ; then
+ if parallel --version 2>&1 | grep -q GNU ; then
echo "INFO: running tests with GNU parallel"
printf '%s\n' $TESTS | $TEST_TIMEOUT_CMD parallel
else
void *ctx = talloc_new (NULL);
const char *config_path = NULL;
- notmuch_config_t *config;
notmuch_database_t *notmuch;
int num_messages = 500;
exit (1);
}
- config = notmuch_config_open (ctx, config_path, false);
- if (config == NULL)
- return 1;
-
- if (notmuch_database_open (notmuch_config_get_database_path (config),
- NOTMUCH_DATABASE_MODE_READ_WRITE, ¬much))
+ if (notmuch_database_open_with_config (NULL,
+ NOTMUCH_DATABASE_MODE_READ_WRITE,
+ config_path,
+ NULL,
+ ¬much,
+ NULL))
return 1;
srandom (seed);
int count;
+
for (count = 0; count < num_messages; count++) {
int j;
/* explicitly allow zero tags */
--- /dev/null
+# .notmuch-config - Configuration file for the notmuch mail system
+#
+# For more information about notmuch, see https://notmuchmail.org
+
+# Database configuration
+#
+# The only value supported here is 'path' which should be the top-level
+# directory where your mail currently exists and to where mail will be
+# delivered in the future. Files should be individual email messages.
+# Notmuch will store its database within a sub-directory of the path
+# configured here named ".notmuch".
+#
+[database]
+path=/path/to/maildir
+
+# User configuration
+#
+# Here is where you can let notmuch know how you would like to be
+# addressed. Valid settings are
+#
+# name Your full name.
+# primary_email Your primary email address.
+# other_email A list (separated by ';') of other email addresses
+# at which you receive email.
+#
+# Notmuch will use the various email addresses configured here when
+# formatting replies. It will avoid including your own addresses in the
+# recipient list of replies, and will set the From address based on the
+# address to which the original email was addressed.
+#
+[user]
+name=Test Suite
+primary_email=test.suite@example.com
+other_email=another.suite@example.com;
+
+# Configuration for "notmuch new"
+#
+# The following options are supported here:
+#
+# tags A list (separated by ';') of the tags that will be
+# added to all messages incorporated by "notmuch new".
+#
+# ignore A list (separated by ';') of file and directory names
+# that will not be searched for messages by "notmuch new".
+#
+# NOTE: *Every* file/directory that goes by one of those
+# names will be ignored, independent of its depth/location
+# in the mail store.
+#
+[new]
+tags=foo;bar;
+
+# Search configuration
+#
+# The following option is supported here:
+#
+# exclude_tags
+# A ;-separated list of tags that will be excluded from
+# search results by default. Using an excluded tag in a
+# query will override that exclusion.
+#
+[search]
+exclude_tags=baz;
+
+# Maildir compatibility configuration
+#
+# The following option is supported here:
+#
+# synchronize_flags Valid values are true and false.
+#
+# If true, then the following maildir flags (in message filenames)
+# will be synchronized with the corresponding notmuch tags:
+#
+# Flag Tag
+# ---- -------
+# D draft
+# F flagged
+# P passed
+# R replied
+# S unread (added when 'S' flag is not present)
+#
+# The "notmuch new" command will notice flag changes in filenames
+# and update tags, while the "notmuch tag" and "notmuch restore"
+# commands will notice tag changes and update flags in filenames
+#
+[maildir]
-;; test-lib.el --- auxiliary stuff for Notmuch Emacs tests.
+;;; test-lib.el --- auxiliary stuff for Notmuch Emacs tests
;;
;; Copyright © Carl Worth
;; Copyright © David Edmondson
;;
;; Authors: Dmitry Kurochkin <dmitry.kurochkin@gmail.com>
+;;; Code:
+
(require 'cl-lib)
;; Ensure that the dynamic variables that are defined by this library
(add-hook 'notmuch-hello-refresh-hook
(lambda () (cl-incf notmuch-hello-refresh-hook-counter)))
+(defvar notmuch-test-tag-hook-output nil)
+(defun notmuch-test-tag-hook () (push (cons query tag-changes) notmuch-test-tag-hook-output))
+
(defun notmuch-test-mark-links ()
"Enclose links in the current buffer with << and >>."
;; Links are often created by jit-lock functions
unset GREP_OPTIONS
+# For lib/open.cc:_load_key_file
+unset XDG_CONFIG_HOME
+
# For emacsclient
unset ALTERNATE_EDITOR
+# for reproducibility
+unset EMAIL
+
add_gnupg_home ()
{
[ -e "${GNUPGHOME}/gpg.conf" ] && return
add_gpgsm_home ()
{
+ test_require_external_prereq openssl
+
local fpr
[ -e "$GNUPGHOME/gpgsm.conf" ] && return
_gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
sed 's/^built_with[.]\(.*\)=.*$/built_with.\1=something/'
}
+notmuch_passwd_sanitize()
+{
+ ${NOTMUCH_PYTHON} -c'
+import os, sys, pwd, socket
+
+pw = pwd.getpwuid(os.getuid())
+user = pw.pw_name
+name = pw.pw_gecos.partition(",")[0]
+fqdn = socket.getfqdn()
+
+for l in sys.stdin:
+ l = l.replace(user, "USERNAME").replace(fqdn, "FQDN").replace(".(none)","").replace(name, "USER_FULL_NAME")
+ sys.stdout.write(l)
+'
+}
+
notmuch_config_sanitize ()
{
notmuch_dir_sanitize | notmuch_built_with_sanitize
base_name="$1"
shift
shim_file="${base_name}.so"
- LD_PRELOAD=./${shim_file}${LD_PRELOAD:+:$LD_PRELOAD} notmuch-shared "$@"
+ LD_PRELOAD=${LD_PRELOAD:+:$LD_PRELOAD}:./${shim_file} notmuch-shared "$@"
}
# Creates a script that counts how much time it is executed and calls
test_declare_external_prereq openssl
test_declare_external_prereq gpgsm
test_declare_external_prereq ${NOTMUCH_PYTHON}
+test_declare_external_prereq xapian-metadata
+test_declare_external_prereq xapian-delve
libnotmuch_util_c_srcs := $(dir)/xutil.c $(dir)/error_util.c $(dir)/hex-escape.c \
$(dir)/string-util.c $(dir)/talloc-extra.c $(dir)/zlib-extra.c \
$(dir)/util.c $(dir)/gmime-extra.c $(dir)/crypto.c \
- $(dir)/repair.c \
+ $(dir)/repair.c $(dir)/path-util.c \
$(dir)/unicode-util.c
libnotmuch_util_modules := $(libnotmuch_util_c_srcs:.c=.o)
notmuch_message_properties_t *list = NULL;
for (list = notmuch_message_get_properties (message, "session-key", TRUE);
- notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
+ notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (
+ list)) {
if (err && *err) {
g_error_free (*err);
*err = NULL;
notmuch_message_properties_value (list),
decrypt_result, err);
} else if (GMIME_IS_APPLICATION_PKCS7_MIME (part)) {
- GMimeApplicationPkcs7Mime *pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part);
+ GMimeApplicationPkcs7Mime *pkcs7 = GMIME_APPLICATION_PKCS7_MIME (part);
GMimeSecureMimeType type = g_mime_application_pkcs7_mime_get_smime_type (pkcs7);
if (type == GMIME_SECURE_MIME_TYPE_ENVELOPED_DATA) {
ret = g_mime_application_pkcs7_mime_decrypt (pkcs7,
GMIME_DECRYPT_NONE,
- notmuch_message_properties_value (list),
+ notmuch_message_properties_value (
+ list),
decrypt_result, err);
}
}
if (attempted)
*attempted = true;
GMimeDecryptFlags flags = GMIME_DECRYPT_NONE;
+
if (decrypt == NOTMUCH_DECRYPT_TRUE && decrypt_result)
flags |= GMIME_DECRYPT_EXPORT_SESSION_KEY;
if (GMIME_IS_MULTIPART_ENCRYPTED (part)) {
}
notmuch_status_t
-_notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypto, GMimeSignatureList *sigs)
+_notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypto,
+ GMimeSignatureList *sigs)
{
if (! msg_crypto)
return NOTMUCH_STATUS_NULL_POINTER;
bool
-_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum)
+_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part,
+ GMimeObject *parent, int childnum)
{
const char *protected_headers = NULL;
const char *forwarded = NULL;
* encryption protocol should be "control information" metadata,
* not payload. So we skip it. (see
* https://tools.ietf.org/html/rfc1847#page-8) */
- if (parent && GMIME_IS_MULTIPART_ENCRYPTED (parent) && childnum == GMIME_MULTIPART_ENCRYPTED_VERSION) {
+ if (parent && GMIME_IS_MULTIPART_ENCRYPTED (parent) && childnum ==
+ GMIME_MULTIPART_ENCRYPTED_VERSION) {
const char *enc_type = g_mime_object_get_content_type_parameter (parent, "protocol");
GMimeContentType *ct = g_mime_object_get_content_type (part);
if (ct && enc_type) {
* consider a particular signature as relevant for the message.
*/
notmuch_status_t
-_notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypto, GMimeSignatureList *sigs);
+_notmuch_message_crypto_potential_sig_list (_notmuch_message_crypto_t *msg_crypto,
+ GMimeSignatureList *sigs);
/* call successful_decryption during a depth-first-search on a message
* to indicate that a part was successfully decrypted.
* this message.
*/
bool
-_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part, GMimeObject *parent, int childnum);
+_notmuch_message_crypto_potential_payload (_notmuch_message_crypto_t *msg_crypto, GMimeObject *part,
+ GMimeObject *parent, int childnum);
#ifdef __cplusplus
if (uid == NULL)
return uid;
GMimeValidity validity = g_mime_certificate_get_id_validity (cert);
+
if (validity == GMIME_VALIDITY_FULL || validity == GMIME_VALIDITY_ULTIMATE)
return uid;
return NULL;
--- /dev/null
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define _GNU_SOURCE
+
+#include "path-util.h"
+
+#include <limits.h>
+#include <stdlib.h>
+
+
+char *
+notmuch_canonicalize_file_name (const char *path)
+{
+#if HAVE_CANONICALIZE_FILE_NAME
+ return canonicalize_file_name (path);
+#elif defined(PATH_MAX)
+ char *resolved_path = malloc (PATH_MAX + 1);
+ if (resolved_path == NULL)
+ return NULL;
+
+ return realpath (path, resolved_path);
+#else
+#error undefined PATH_MAX _and_ missing canonicalize_file_name not supported
+#endif
+}
--- /dev/null
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#ifndef NOTMUCH_UTIL_PATH_UTIL_H_
+#define NOTMUCH_UTIL_PATH_UTIL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+char *
+notmuch_canonicalize_file_name (const char *path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NOTMUCH_UTIL_PATH_UTIL_H_ */
if (! g_mime_content_type_is_type (g_mime_object_get_content_type (payload),
"multipart", "mixed"))
return false;
- protected_header_parameter = g_mime_object_get_content_type_parameter (payload, "protected-headers");
+ protected_header_parameter = g_mime_object_get_content_type_parameter (payload,
+ "protected-headers");
if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1"))
return false;
if (! GMIME_IS_MULTIPART (payload))
return false;
first = g_mime_multipart_get_part (mpayload, 0);
/* Early implementations that generated "Legacy Display" parts used
- Content-Type: text/rfc822-headers, but text/plain is more widely
- rendered, so it is now the standard choice. We accept either as a
- Legacy Display part. */
+ * Content-Type: text/rfc822-headers, but text/plain is more widely
+ * rendered, so it is now the standard choice. We accept either as a
+ * Legacy Display part. */
if (! (g_mime_content_type_is_type (g_mime_object_get_content_type (first),
"text", "plain") ||
g_mime_content_type_is_type (g_mime_object_get_content_type (first),
"text", "rfc822-headers")))
return false;
- protected_header_parameter = g_mime_object_get_content_type_parameter (first, "protected-headers");
+ protected_header_parameter = g_mime_object_get_content_type_parameter (first,
+ "protected-headers");
if ((! protected_header_parameter) || strcmp (protected_header_parameter, "v1"))
return false;
if (! GMIME_IS_TEXT_PART (first))
_notmuch_is_mixed_up_mangled (GMimeObject *part)
{
GMimeMultipart *mpart = NULL;
- GMimeObject *parts[3] = {NULL, NULL, NULL};
+ GMimeObject *parts[3] = { NULL, NULL, NULL };
GMimeContentType *type = NULL;
char *prelude_string = NULL;
bool prelude_is_empty;
#include <ctype.h>
#include <errno.h>
+#include <stdbool.h>
char *
strtok_len (char *s, const char *delim, size_t *len)
return *len ? s : NULL;
}
+const char *
+strsplit_len (const char *s, char delim, size_t *len)
+{
+ bool escaping = false;
+ size_t count = 0;
+
+ /* Skip initial unescaped delimiters */
+ while (*s && *s == delim)
+ s++;
+
+ while (s[count] && (escaping || s[count] != delim)) {
+ escaping = (s[count] == '\\');
+ count++;
+ }
+
+ if (count == 0)
+ return NULL;
+
+ *len = count;
+ return s;
+}
+
const char *
strtok_len_c (const char *s, const char *delim, size_t *len)
{
/* Parse prefix */
str = skip_space (str);
const char *pos = strchr (str, ':');
+
if (! pos || pos == str)
goto FAIL;
*prefix_out = talloc_strndup (ctx, str, pos - str);
/* Const version of strtok_len. */
const char *strtok_len_c (const char *s, const char *delim, size_t *len);
+/* Simplified version of strtok_len, with a single delimiter.
+ * Handles escaping delimiters with \
+ * Usage pattern:
+ *
+ * const char *tok = input;
+ * const char *delim = ';';
+ * size_t tok_len = 0;
+ *
+ * while ((tok = strsplit_len (tok + tok_len, delim, &tok_len)) != NULL) {
+ * // do stuff with string tok of length tok_len
+ * }
+ */
+const char *strsplit_len (const char *s, char delim, size_t *len);
+
/* Return a talloced string with str sanitized.
*
* Whitespace characters (tabs and newlines) are replaced with spaces,
}
const char *
-gzerror_str(gzFile file)
+gzerror_str (gzFile file)
{
int dummy;
+
return gzerror (file, &dummy);
}
/* Call gzerror with a dummy errno argument, the docs don't promise to
* support the NULL case */
const char *
-gzerror_str(gzFile file);
+gzerror_str (gzFile file);
#ifdef __cplusplus
}
date = Time.at(e.newest_date).strftime(date_fmt)
subject = e.messages.first['subject']
if $mail_installed
- subject = Mail::Field.new("Subject: " + subject).to_s
+ subject = Mail::Field.parse("Subject: " + subject).to_s
else
subject = subject.force_encoding('utf-8')
end