-.first-build-message
-Makefile.config
-sh.config
-version.stamp
+/.first-build-message
+/Makefile.config
+/sh.config
+/version.stamp
TAGS
tags
*cscope*
-.deps
+/.deps
/notmuch
-notmuch-shared
-libnotmuch.so*
-libnotmuch*.dylib
+/notmuch-shared
+/lib/libnotmuch.so*
+/lib/libnotmuch*.dylib
*.[ao]
*~
.*.swp
-releases
+/releases
+/.stamps
language: c
-before_install:
- - sudo apt-get update -qq
- - sudo apt-get install dtach libxapian-dev libgmime-2.6-dev libtalloc-dev python-sphinx
- # Notmuch requires zlib 1.2.5.2, unfortunately travis runs on Ubuntu 12.04LTS which
- # ships with zlib 1.2.3.3. We need to update to zlib 1.2.5.2 to be able to build.
- # TODO: Watch https://github.com/travis-ci/travis-ci/issues/2046 and remove
- # this hack once travis-ci switches to Ubuntu 14.04
- - wget 'https://github.com/notmuch/travis-files/raw/master/zlib1g-dev_1.2.8.dfsg-1ubuntu1_amd64.deb'
- - wget 'https://github.com/notmuch/travis-files/raw/master/zlib1g_1.2.8.dfsg-1ubuntu1_amd64.deb'
- - sudo dpkg -i zlib1g-dev_1.2.8.dfsg-1ubuntu1_amd64.deb zlib1g_1.2.8.dfsg-1ubuntu1_amd64.deb
- - sudo apt-get install -f
+dist: trusty
+sudo: false
+
+addons:
+ apt:
+ packages:
+ - dtach
+ - libxapian-dev
+ - libgmime-2.6-dev
+ - libtalloc-dev
+ - python-sphinx
+ - gdb
+ - gpgsm
script:
- ./configure
+ - make download-test-databases
- make test
notifications:
# user how to enable verbose compiles.
ifeq ($(V),)
quiet_DOC := "Use \"$(MAKE) V=1\" to see the verbose compile lines.\n"
-quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)"$1 $@\n"; $($(shell echo $1 | sed -e s'/ .*//'))
+quiet = @printf $(quiet_DOC)$(eval quiet_DOC:=)"$(1) $(or $(2),$@)\n"; $($(word 1, $(1)))
endif
# The user has explicitly enabled quiet compilation.
ifeq ($(V),0)
-quiet = @printf "$1 $@\n"; $($(shell echo $1 | sed -e s'/ .*//'))
+quiet = @printf "$(1) $(or $(2),$@)\n"; $($(word 1, $(1)))
endif
# Otherwise, print the full command line.
-quiet ?= $($(shell echo $1 | sed -e s'/ .*//'))
+quiet ?= $($(word 1, $(1)))
%.o: %.cc $(global_deps)
@mkdir -p $(patsubst %/.,%,.deps/$(@D))
@mkdir -p $(patsubst %/.,%,.deps/$(@D))
$(call quiet,CC $(CPPFLAGS) $(CFLAGS)) -c $(FINAL_CFLAGS) $< -o $@ -MD -MP -MF .deps/$*.d
+CPPCHECK=cppcheck
+.stamps/cppcheck/%: %
+ @mkdir -p $(@D)
+ $(call quiet,CPPCHECK,$<) --template=gcc --error-exitcode=1 --quiet $<
+ @touch $@
+
+CLEAN := $(CLEAN) .stamps
+
.PHONY : clean
clean:
- rm -rf $(CLEAN); rm -rf .deps
+ rm -rf $(CLEAN)
.PHONY: distclean
distclean: clean
notmuch-dump.c \
notmuch-insert.c \
notmuch-new.c \
+ notmuch-reindex.c \
notmuch-reply.c \
notmuch-restore.c \
notmuch-search.c \
sprinter-text.c \
query-string.c \
mime-node.c \
- crypto.c \
tag-util.c
notmuch_client_modules = $(notmuch_client_srcs:.c=.o)
SRCS := $(SRCS) $(notmuch_client_srcs)
CLEAN := $(CLEAN) notmuch notmuch-shared $(notmuch_client_modules)
CLEAN := $(CLEAN) version.stamp notmuch-*.tar.gz.tmp
+CLEAN := $(CLEAN) .deps
DISTCLEAN := $(DISTCLEAN) .first-build-message Makefile.config sh.config
+CPPCHECK_STAMPS := $(SRCS:%=.stamps/cppcheck/%)
+.PHONY: cppcheck
+ifeq ($(HAVE_CPPCHECK),1)
+cppcheck: ${CPPCHECK_STAMPS}
+else
+cppcheck:
+ @echo "No cppcheck found during configure; skipping static checking"
+endif
+
+
DEPS := $(SRCS:%.c=.deps/%.d)
DEPS := $(DEPS:%.cc=.deps/%.d)
-include $(DEPS)
+Notmuch 0.26 (UNRELEASED)
+=========================
+
+Test Suite
+----------
+
+Out-of-tree builds
+
+ The test suite now works properly with out-of-tree builds, i.e. with
+ separate source and build directories. The --root option to tests
+ has been dropped. The same can now be achieved more reliably using
+ out-of-tree builds.
+
+Encrypted Mail
+--------------
+
+Indexing cleartext of encrypted e-mails
+
+ It's now possible to include the cleartext of encrypted e-mails in
+ the notmuch index. This makes it possible to search your encrypted
+ e-mails with the same ease as searching cleartext. This can be done
+ on a per-message basis with the --try-decrypt argument to indexing
+ commands (new, insert, reindex), or by default by running "notmuch
+ config set index.try_decrypt true".
+
+ Note that the contents of the index are sufficient to roughly
+ reconstruct the cleartext of the message itself, so please ensure
+ that the notmuch index itself is adequately protected. DO NOT USE
+ this feature without considering the security of your index.
+
Notmuch 0.25.2 (2017-11-05)
===========================
*.py[co]
/docs/build
/docs/html
-build/
+/build/
This module makes the functionality of the notmuch library
(`https://notmuchmail.org`_) available to python. Successful import of
-this modul depends on a libnotmuch.so|dll being available on the
+this module depends on a libnotmuch.so|dll being available on the
user's system.
If you have downloaded the full source tarball, you can create the
.. automethod:: get_directory
- .. automethod:: add_message
+ .. automethod:: index_file
.. automethod:: remove_message
import os
import codecs
+import warnings
from ctypes import c_char_p, c_void_p, c_uint, byref, POINTER
from .compat import SafeConfigParser
from .globals import (
"""Does this database need to be upgraded before writing to it?
If this function returns `True` then no functions that modify the
- database (:meth:`add_message`,
+ database (:meth:`index_file`,
:meth:`Message.add_tag`, :meth:`Directory.set_mtime`,
etc.) will work unless :meth:`upgrade` is called successfully first.
# return the Directory, init it with the absolute path
return Directory(abs_dirpath, dir_p, self)
- _add_message = nmlib.notmuch_database_add_message
- _add_message.argtypes = [NotmuchDatabaseP, c_char_p,
+ _index_file = nmlib.notmuch_database_index_file
+ _index_file.argtypes = [NotmuchDatabaseP, c_char_p,
+ c_void_p,
POINTER(NotmuchMessageP)]
- _add_message.restype = c_uint
+ _index_file.restype = c_uint
- def add_message(self, filename, sync_maildir_flags=False):
+ def index_file(self, filename, sync_maildir_flags=False):
"""Adds a new message to the database
:param filename: should be a path relative to the path of the
"""
self._assert_db_is_initialized()
msg_p = NotmuchMessageP()
- status = self._add_message(self._db, _str(filename), byref(msg_p))
+ status = self._index_file(self._db, _str(filename), c_void_p(None), byref(msg_p))
if not status in [STATUS.SUCCESS, STATUS.DUPLICATE_MESSAGE_ID]:
raise NotmuchError(status)
msg.maildir_flags_to_tags()
return (msg, status)
+ def add_message(self, filename, sync_maildir_flags=False):
+ """Deprecated alias for :meth:`index_file`
+ """
+ warnings.warn(
+ "This function is deprecated and will be removed in the future, use index_file.", DeprecationWarning)
+
+ return self.index_file(filename, sync_maildir_flags=sync_maildir_flags)
+
_remove_message = nmlib.notmuch_database_remove_message
_remove_message.argtypes = [NotmuchDatabaseP, c_char_p]
_remove_message.restype = c_uint
* Read the mtime of a directory from the filesystem
- * Call :meth:`Database.add_message` for all mail files in
+ * Call :meth:`Database.index_file` for all mail files in
the directory
* Call notmuch_directory_set_mtime with the mtime read from the
from .filenames import Filenames
import email
+import sys
class Message(Python3StringMixIn):
logical OR operator.)
As a convenience, you can set the sync_maildir_flags parameter in
- :meth:`Database.add_message` to implicitly call this.
+ :meth:`Database.index_file` to implicitly call this.
:returns: a :class:`STATUS`. In short, you want to see
notmuch.STATUS.SUCCESS here. See there for details."""
def get_message_parts(self):
"""Output like notmuch show"""
- fp = open(self.get_filename())
- email_msg = email.message_from_file(fp)
+ fp = open(self.get_filename(), 'rb')
+ if sys.version_info[0] < 3:
+ email_msg = email.message_from_file(fp)
+ else:
+ email_msg = email.message_from_binary_file(fp)
fp.close()
out = []
# .gitignore for bindings/ruby
# Generated files
-Makefile
-mkmf.log
-notmuch.so
+/Makefile
+/mkmf.log
+/notmuch.so
*.o
SafeStringValue (pathv);
path = RSTRING_PTR (pathv);
- ret = notmuch_database_add_message (db, path, &message);
+ ret = notmuch_database_index_file (db, path, NULL, &message);
notmuch_rb_status_raise (ret);
return rb_assoc_new (Data_Wrap_Struct (notmuch_rb_cMessage, NULL, NULL, message),
(ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) ? Qtrue : Qfalse);
/*
Search the array of keywords for a given argument, assigning the
- output variable to the corresponding value. Return FALSE if nothing
+ output variable to the corresponding value. Return false if nothing
matches.
*/
-static notmuch_bool_t
+static bool
_process_keyword_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
- const notmuch_keyword_t *keywords = arg_desc->keywords;
+ const notmuch_keyword_t *keywords;
if (next == '\0') {
/* No keyword given */
arg_str = "";
}
- while (keywords->name) {
- if (strcmp (arg_str, keywords->name) == 0) {
- if (arg_desc->output_var) {
- if (arg_desc->opt_type == NOTMUCH_OPT_KEYWORD_FLAGS)
- *((int *)arg_desc->output_var) |= keywords->value;
- else
- *((int *)arg_desc->output_var) = keywords->value;
- }
- return TRUE;
- }
- keywords++;
+ for (keywords = arg_desc->keywords; keywords->name; keywords++) {
+ if (strcmp (arg_str, keywords->name) != 0)
+ continue;
+
+ if (arg_desc->opt_flags)
+ *arg_desc->opt_flags |= keywords->value;
+ else
+ *arg_desc->opt_keyword = keywords->value;
+
+ return true;
}
if (next != '\0')
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 FALSE;
+ return false;
}
-static notmuch_bool_t
+static bool
_process_boolean_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
-
- if (next == '\0') {
- *((notmuch_bool_t *)arg_desc->output_var) = TRUE;
- return TRUE;
- }
- if (strcmp (arg_str, "false") == 0) {
- *((notmuch_bool_t *)arg_desc->output_var) = FALSE;
- return TRUE;
- }
- if (strcmp (arg_str, "true") == 0) {
- *((notmuch_bool_t *)arg_desc->output_var) = TRUE;
- return TRUE;
+ bool value;
+
+ if (next == '\0' || strcmp (arg_str, "true") == 0) {
+ value = true;
+ } 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);
+ return false;
}
- fprintf (stderr, "Unknown argument \"%s\" for (boolean) option \"%s\".\n", arg_str, arg_desc->name);
- return FALSE;
+
+ *arg_desc->opt_bool = value;
+
+ return true;
}
-static notmuch_bool_t
+static bool
_process_int_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
char *endptr;
if (next == '\0' || arg_str[0] == '\0') {
fprintf (stderr, "Option \"%s\" needs an integer argument.\n", arg_desc->name);
- return FALSE;
+ return false;
}
- *((int *)arg_desc->output_var) = strtol (arg_str, &endptr, 10);
+ *arg_desc->opt_int = strtol (arg_str, &endptr, 10);
if (*endptr == '\0')
- return TRUE;
+ return true;
fprintf (stderr, "Unable to parse argument \"%s\" for option \"%s\" as an integer.\n",
arg_str, arg_desc->name);
- return FALSE;
+ return false;
}
-static notmuch_bool_t
+static bool
_process_string_arg (const notmuch_opt_desc_t *arg_desc, char next, const char *arg_str) {
if (next == '\0') {
fprintf (stderr, "Option \"%s\" needs a string argument.\n", arg_desc->name);
- return FALSE;
+ return false;
}
if (arg_str[0] == '\0') {
fprintf (stderr, "String argument for option \"%s\" must be non-empty.\n", arg_desc->name);
- return FALSE;
+ return false;
}
- *((const char **)arg_desc->output_var) = arg_str;
- return TRUE;
+ *arg_desc->opt_string = arg_str;
+ return true;
+}
+
+/* Return number of non-NULL opt_* fields in opt_desc. */
+static int _opt_set_count (const notmuch_opt_desc_t *opt_desc)
+{
+ return
+ !!opt_desc->opt_inherit +
+ !!opt_desc->opt_bool +
+ !!opt_desc->opt_int +
+ !!opt_desc->opt_keyword +
+ !!opt_desc->opt_flags +
+ !!opt_desc->opt_string +
+ !!opt_desc->opt_position;
+}
+
+/* Return true if opt_desc is valid. */
+static bool _opt_valid (const notmuch_opt_desc_t *opt_desc)
+{
+ int n = _opt_set_count (opt_desc);
+
+ if (n > 1)
+ INTERNAL_ERROR ("more than one non-NULL opt_* field for argument \"%s\"",
+ opt_desc->name);
+
+ return n > 0;
}
/*
- Search for the {pos_arg_index}th position argument, return FALSE if
+ Search for the {pos_arg_index}th position argument, return false if
that does not exist.
*/
-notmuch_bool_t
+bool
parse_position_arg (const char *arg_str, int pos_arg_index,
const notmuch_opt_desc_t *arg_desc) {
int pos_arg_counter = 0;
- while (arg_desc->opt_type != NOTMUCH_OPT_END){
- if (arg_desc->opt_type == NOTMUCH_OPT_POSITION) {
+ while (_opt_valid (arg_desc)) {
+ if (arg_desc->opt_position) {
if (pos_arg_counter == pos_arg_index) {
- if (arg_desc->output_var) {
- *((const char **)arg_desc->output_var) = arg_str;
- }
- return TRUE;
+ *arg_desc->opt_position = arg_str;
+ if (arg_desc->present)
+ *arg_desc->present = true;
+ return true;
}
pos_arg_counter++;
}
arg_desc++;
}
- return FALSE;
+ return false;
}
/*
if (opt_index < argc - 1 && strncmp (argv[opt_index + 1], "--", 2) != 0)
next_arg = argv[opt_index + 1];
- for (try = options; try->opt_type != NOTMUCH_OPT_END; try++) {
- if (try->opt_type == NOTMUCH_OPT_INHERIT) {
- int new_index = parse_option (argc, argv, try->output_var, opt_index);
+ for (try = options; _opt_valid (try); try++) {
+ if (try->opt_inherit) {
+ int new_index = parse_option (argc, argv, try->opt_inherit, opt_index);
if (new_index >= 0)
return new_index;
}
if (next != '=' && next != ':' && next != '\0')
continue;
- if (next == '\0' && next_arg != NULL && try->opt_type != NOTMUCH_OPT_BOOLEAN) {
+ if (next == '\0' && next_arg != NULL && ! try->opt_bool) {
next = ' ';
value = next_arg;
opt_index ++;
}
- if (try->output_var == NULL)
- INTERNAL_ERROR ("output pointer NULL for option %s", try->name);
-
- notmuch_bool_t opt_status = FALSE;
- switch (try->opt_type) {
- case NOTMUCH_OPT_KEYWORD:
- case NOTMUCH_OPT_KEYWORD_FLAGS:
+ bool opt_status = false;
+ if (try->opt_keyword || try->opt_flags)
opt_status = _process_keyword_arg (try, next, value);
- break;
- case NOTMUCH_OPT_BOOLEAN:
+ else if (try->opt_bool)
opt_status = _process_boolean_arg (try, next, value);
- break;
- case NOTMUCH_OPT_INT:
+ else if (try->opt_int)
opt_status = _process_int_arg (try, next, value);
- break;
- case NOTMUCH_OPT_STRING:
+ else if (try->opt_string)
opt_status = _process_string_arg (try, next, value);
- break;
- case NOTMUCH_OPT_POSITION:
- case NOTMUCH_OPT_END:
- default:
- INTERNAL_ERROR ("unknown or unhandled option type %d", try->opt_type);
- /*UNREACHED*/
- }
- if (opt_status)
- return opt_index+1;
else
+ INTERNAL_ERROR ("unknown or unhandled option \"%s\"", try->name);
+
+ if (! opt_status)
return -1;
+
+ if (try->present)
+ *try->present = true;
+
+ return opt_index+1;
}
return -1;
}
const notmuch_opt_desc_t *options, int opt_index) {
int pos_arg_index = 0;
- notmuch_bool_t more_args = TRUE;
+ bool more_args = true;
while (more_args && opt_index < argc) {
if (strncmp (argv[opt_index],"--",2) != 0) {
opt_index = parse_option (argc, argv, options, opt_index);
if (opt_index < 0) {
fprintf (stderr, "Unrecognized option: %s\n", argv[prev_opt_index]);
- more_args = FALSE;
+ more_args = false;
}
}
}
#ifndef NOTMUCH_OPTS_H
#define NOTMUCH_OPTS_H
-#include "notmuch.h"
+#include <stdbool.h>
-enum notmuch_opt_type {
- NOTMUCH_OPT_END = 0,
- NOTMUCH_OPT_INHERIT, /* another options table */
- NOTMUCH_OPT_BOOLEAN, /* --verbose */
- NOTMUCH_OPT_INT, /* --frob=8 */
- NOTMUCH_OPT_KEYWORD, /* --format=raw|json|text */
- NOTMUCH_OPT_KEYWORD_FLAGS, /* the above with values OR'd together */
- NOTMUCH_OPT_STRING, /* --file=/tmp/gnarf.txt */
- NOTMUCH_OPT_POSITION /* notmuch dump pos_arg */
-};
+#include "notmuch.h"
/*
* Describe one of the possibilities for a keyword option
int value;
} notmuch_keyword_t;
-/*
- * Describe one option.
- *
- * First two parameters are mandatory.
- *
- * name is mandatory _except_ for positional arguments.
- *
- * arg_id is currently unused, but could define short arguments.
- *
- * keywords is a (possibly NULL) pointer to an array of keywords
- */
+/* Describe one option. */
typedef struct notmuch_opt_desc {
- enum notmuch_opt_type opt_type;
- void *output_var;
+ /* One and only one of opt_* must be set. */
+ const struct notmuch_opt_desc *opt_inherit;
+ bool *opt_bool;
+ int *opt_int;
+ int *opt_keyword;
+ int *opt_flags;
+ const char **opt_string;
+ const char **opt_position;
+
+ /* Must be set except for opt_inherit and opt_position. */
const char *name;
- int arg_id;
+
+ /* Optional, if non-NULL, set to true if the option is present. */
+ bool *present;
+
+ /* Must be set for opt_keyword and opt_flags. */
const struct notmuch_keyword *keywords;
} notmuch_opt_desc_t;
int
parse_option (int argc, char **argv, const notmuch_opt_desc_t* options, int opt_index);
-notmuch_bool_t
+bool
parse_position_arg (const char *arg,
int position_arg_index,
const notmuch_opt_desc_t* options);
sed "s|^$path/||" | grep -v "\(^\|/\)\(cur\|new\|tmp\)$" ) )
return
;;
+ --try-decrypt)
+ COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+ return
+ ;;
esac
! $split &&
case "${cur}" in
--*)
- local options="--create-folder --folder= --keep --no-hooks ${_notmuch_shared_options}"
+ local options="--create-folder --folder= --keep --no-hooks --try-decrypt= ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
return
_notmuch_new()
{
local cur prev words cword split
- _init_completion || return
+ _init_completion -s || return
+
+ $split &&
+ case "${prev}" in
+ --try-decrypt)
+ COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+ return
+ ;;
+ esac
+ ! $split &&
case "${cur}" in
-*)
- local options="--no-hooks --quiet ${_notmuch_shared_options}"
+ local options="--no-hooks --try-decrypt= --quiet ${_notmuch_shared_options}"
compopt -o nospace
COMPREPLY=( $(compgen -W "${options}" -- ${cur}) )
;;
esac
}
+_notmuch_reindex()
+{
+ local cur prev words cword split
+ _init_completion -s || return
+
+ $split &&
+ case "${prev}" in
+ --try-decrypt)
+ COMPREPLY=( $( compgen -W "true false" -- "${cur}" ) )
+ return
+ ;;
+ esac
+
+ ! $split &&
+ case "${cur}" in
+ -*)
+ local options="--try-decrypt= ${_notmuch_shared_options}"
+ compopt -o nospace
+ COMPREPLY=( $(compgen -W "$options" -- ${cur}) )
+ ;;
+ *)
+ _notmuch_search_terms
+ ;;
+ esac
+}
+
_notmuch_address()
{
local cur prev words cword split
_notmuch()
{
- local _notmuch_commands="compact config count dump help insert new reply restore search address setup show tag emacs-mua"
+ local _notmuch_commands="compact config count dump help insert new reply restore reindex search address setup show tag emacs-mua"
local arg cur prev words cword split
# require bash-completion with _init_completion
cp "$srcdir"/"$dir"/Makefile "$dir"
done
- # Easiest way to get the test suite to work is to just copy the
- # whole thing into the build directory.
- cp -a "$srcdir"/test/* test
-
# Emacs only likes to generate compiled files next to the .el files
# by default so copy these as well (which is not ideal).
cp -a "$srcdir"/emacs/*.el emacs
fi
fi
+printf "Checking for cppcheck... "
+if command -v cppcheck > /dev/null; then
+ have_cppcheck=1
+ printf "Yes.\n"
+else
+ have_cppcheck=0
+ printf "No.\n"
+fi
+
libdir_in_ldconfig=0
printf "Checking which platform we are on... "
# build its own version)
HAVE_CANONICALIZE_FILE_NAME = ${have_canonicalize_file_name}
+# Whether the cppcheck static checker is available
+HAVE_CPPCHECK = ${have_cppcheck}
+
# Whether the getline function is available (if not, then notmuch will
# build its own version)
HAVE_GETLINE = ${have_getline}
-src/github.com/
-pkg/
-bin/
+/src/github.com/
+/pkg/
+/bin/
/* Does this database need to be upgraded before writing to it?
*
* If this function returns TRUE then no functions that modify the
- * database (notmuch_database_add_message, notmuch_message_add_tag,
+ * database (notmuch_database_index_file, notmuch_message_add_tag,
* notmuch_directory_set_mtime, etc.) will work unless the function
* notmuch_database_upgrade is called successfully first. */
func (self *Database) NeedsUpgrade() bool {
-notmuch-mutt.1
-README.html
+/notmuch-mutt.1
+/README.html
+++ /dev/null
-/* notmuch - Not much of an email program, (just index and search)
- *
- * Copyright © 2012 Jameson Rollins
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see https://www.gnu.org/licenses/ .
- *
- * Authors: Jameson Rollins <jrollins@finestructure.net>
- */
-
-#include "notmuch-client.h"
-#if (GMIME_MAJOR_VERSION < 3)
-/* Create a GPG context (GMime 2.6) */
-static notmuch_crypto_context_t *
-create_gpg_context (notmuch_crypto_t *crypto)
-{
- notmuch_crypto_context_t *gpgctx;
-
- if (crypto->gpgctx)
- return crypto->gpgctx;
-
- /* TODO: GMimePasswordRequestFunc */
- gpgctx = g_mime_gpg_context_new (NULL, crypto->gpgpath ? crypto->gpgpath : "gpg");
- if (! gpgctx) {
- fprintf (stderr, "Failed to construct gpg context.\n");
- return NULL;
- }
- crypto->gpgctx = gpgctx;
-
- g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) gpgctx, TRUE);
- g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) gpgctx, FALSE);
-
- return gpgctx;
-}
-
-/* Create a PKCS7 context (GMime 2.6) */
-static notmuch_crypto_context_t *
-create_pkcs7_context (notmuch_crypto_t *crypto)
-{
- notmuch_crypto_context_t *pkcs7ctx;
-
- if (crypto->pkcs7ctx)
- return crypto->pkcs7ctx;
-
- /* TODO: GMimePasswordRequestFunc */
- pkcs7ctx = g_mime_pkcs7_context_new (NULL);
- if (! pkcs7ctx) {
- fprintf (stderr, "Failed to construct pkcs7 context.\n");
- return NULL;
- }
- crypto->pkcs7ctx = pkcs7ctx;
-
- g_mime_pkcs7_context_set_always_trust ((GMimePkcs7Context *) pkcs7ctx,
- FALSE);
-
- return pkcs7ctx;
-}
-static const struct {
- const char *protocol;
- notmuch_crypto_context_t *(*get_context) (notmuch_crypto_t *crypto);
-} protocols[] = {
- {
- .protocol = "application/pgp-signature",
- .get_context = create_gpg_context,
- },
- {
- .protocol = "application/pgp-encrypted",
- .get_context = create_gpg_context,
- },
- {
- .protocol = "application/pkcs7-signature",
- .get_context = create_pkcs7_context,
- },
- {
- .protocol = "application/x-pkcs7-signature",
- .get_context = create_pkcs7_context,
- },
-};
-
-/* for the specified protocol return the context pointer (initializing
- * if needed) */
-notmuch_crypto_context_t *
-notmuch_crypto_get_context (notmuch_crypto_t *crypto, const char *protocol)
-{
- notmuch_crypto_context_t *cryptoctx = NULL;
- size_t i;
-
- if (! protocol) {
- fprintf (stderr, "Cryptographic protocol is empty.\n");
- return cryptoctx;
- }
-
- /* As per RFC 1847 section 2.1: "the [protocol] value token is
- * comprised of the type and sub-type tokens of the Content-Type".
- * As per RFC 1521 section 2: "Content-Type values, subtypes, and
- * parameter names as defined in this document are
- * case-insensitive." Thus, we use strcasecmp for the protocol.
- */
- for (i = 0; i < ARRAY_SIZE (protocols); i++) {
- if (strcasecmp (protocol, protocols[i].protocol) == 0)
- return protocols[i].get_context (crypto);
- }
-
- fprintf (stderr, "Unknown or unsupported cryptographic protocol %s.\n",
- protocol);
-
- return NULL;
-}
-
-int
-notmuch_crypto_cleanup (notmuch_crypto_t *crypto)
-{
- if (crypto->gpgctx) {
- g_object_unref (crypto->gpgctx);
- crypto->gpgctx = NULL;
- }
-
- if (crypto->pkcs7ctx) {
- g_object_unref (crypto->pkcs7ctx);
- crypto->pkcs7ctx = NULL;
- }
-
- return 0;
-}
-#else
-int notmuch_crypto_cleanup (unused(notmuch_crypto_t *crypto))
-{
- return 0;
-}
-#endif
-tmp/
-libnotmuch-dev/
-libnotmuch*/
-notmuch-emacs/
-notmuch/
-notmuch-dbg/
-notmuch-mutt/
-notmuch-vim/
-ruby-notmuch/
-python*-notmuch/
-*.debhelper
-*.debhelper.log
-*.substvars
-files
+/tmp/
+/libnotmuch-dev/
+/libnotmuch*/
+/notmuch-emacs/
+/elpa-notmuch/
+/notmuch/
+/notmuch-mutt/
+/notmuch-vim/
+/ruby-notmuch/
+/python*-notmuch/
+/*.debhelper
+/*.debhelper.log
+/*.substvars
+/files
#define RUNNING_ON_VALGRIND 0
#endif
-notmuch_bool_t
+bool
debugger_is_active (void)
{
char buf[1024];
if (RUNNING_ON_VALGRIND)
- return TRUE;
+ return true;
sprintf (buf, "/proc/%d/exe", getppid ());
if (readlink (buf, buf, sizeof (buf)) != -1 &&
strncmp (basename (buf), "gdb", 3) == 0)
{
- return TRUE;
+ return true;
}
- return FALSE;
+ return false;
}
--- /dev/null
+#!/bin/sh
+# test out-of-tree builds in a temp directory
+# passes all args to make
+
+set -eu
+
+srcdir=$(cd "$(dirname "$0")"/.. && pwd)
+builddir=$(mktemp -d)
+
+cd "$builddir"
+
+"$srcdir"/configure
+make "$@"
+
+rm -rf "$builddir"
*.pyc
-_build
-config.dox
+/_build
+/config.dox
# Output file base name for HTML help builder.
htmlhelp_basename = 'notmuchdoc'
+# Disable SmartyPants, as it mangles command lines.
+# Despite the name, this actually affects manual pages as well.
+html_use_smartypants = False
+
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
u'incorporate new mail into the notmuch database',
[notmuch_authors], 1),
+ ('man7/notmuch-properties', 'notmuch-properties',
+ u'notmuch message property conventions and documentation',
+ [notmuch_authors], 7),
+
+ ('man1/notmuch-reindex', 'notmuch-reindex',
+ u're-index matching messages',
+ [notmuch_authors], 1),
+
('man1/notmuch-reply', 'notmuch-reply',
u'constructs a reply template for a set of messages',
[notmuch_authors], 1),
man5/notmuch-hooks
man1/notmuch-insert
man1/notmuch-new
+ man7/notmuch-properties
+ man1/notmuch-reindex
man1/notmuch-reply
man1/notmuch-restore
man1/notmuch-search
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**,
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**,
**notmuch-search(1)**
SEE ALSO
========
-**notmuch(1)**, **notmuch-count(1)**, **notmuch-dump(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
===========
The **config** command can be used to get or set settings in the notmuch
-configuration file.
+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
Default: ``gpg``.
+ **index.try_decrypt**
+
+ **[STORED IN DATABASE]**
+ When indexing an encrypted e-mail message, if this variable is
+ set to true, notmuch will try to decrypt the message and index
+ the cleartext. Be aware that the index is likely sufficient
+ to reconstruct the cleartext of the message itself, so please
+ ensure that the notmuch message index is adequately protected.
+ DO NOT USE ``index.try_decrypt=true`` without considering the
+ security of your index.
+
+ Default: ``false``.
+
**built_with.<name>**
Compile time feature <name>. Current possibilities include
**query.<name>**
+ **[STORED IN DATABASE]**
Expansion for named query called <name>. See
**notmuch-search-terms(7)** for more information about named
queries.
SEE ALSO
========
-**notmuch(1)**, **notmuch-count(1)**, **notmuch-dump(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-dump(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
Output per-message (key,value) metadata. Each line starts
with "#= ", followed by a message id, and a space separated
- list of key=value pairs. pair. Ids, keys and values are hex
- encoded if needed.
+ list of key=value pairs. Ids, keys and values are hex encoded
+ if needed. See **notmuch-properties(7)** for more details.
**tags**
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-hooks(5)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
``--no-hooks``
Prevent hooks from being run.
+ ``--try-decrypt=(true|false)``
+
+ If true and the message is encrypted, try to decrypt the
+ message while indexing. If decryption is successful, index
+ the cleartext itself. Either way, the message is always
+ stored to disk in its original form (ciphertext). Be aware
+ that the index is likely sufficient to reconstruct the
+ cleartext of the message itself, so please ensure that the
+ notmuch message index is adequately protected. DO NOT USE
+ ``--try-decrypt=true`` without considering the security of
+ your index.
+
+ See also ``index.try_decrypt`` in **notmuch-config(1)**.
+
EXIT STATUS
===========
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-reply(1)**,
-**notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
``--quiet``
Do not print progress or results.
+ ``--try-decrypt=(true|false)``
+
+ If true, when encountering an encrypted message, try to
+ decrypt it while indexing. If decryption is successful, index
+ the cleartext itself. Be aware that the index is likely
+ sufficient to reconstruct the cleartext of the message itself,
+ so please ensure that the notmuch message index is adequately
+ protected. DO NOT USE ``--try-decrypt=true`` without
+ considering the security of your index.
+
+ See also ``index.try_decrypt`` in **notmuch-config(1)**.
+
EXIT STATUS
===========
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
--- /dev/null
+===============
+notmuch-reindex
+===============
+
+SYNOPSIS
+========
+
+**notmuch** **reindex** [*option* ...] <*search-term*> ...
+
+DESCRIPTION
+===========
+
+Re-index all messages matching the search terms.
+
+See **notmuch-search-terms(7)** for details of the supported syntax for
+<*search-term*\ >.
+
+The **reindex** command searches for all messages matching the
+supplied search terms, and re-creates the full-text index on these
+messages using the supplied options.
+
+Supported options for **reindex** include
+
+ ``--try-decrypt=(true|false)``
+
+ If true, when encountering an encrypted message, try to
+ decrypt it while reindexing. If decryption is successful,
+ index the cleartext itself. Be aware that the index is likely
+ sufficient to reconstruct the cleartext of the message itself,
+ so please ensure that the notmuch message index is adequately
+ protected. DO NOT USE ``--try-decrypt=true`` without
+ considering the security of your index.
+
+ See also ``index.try_decrypt`` in **notmuch-config(1)**.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
**properties**
- Output per-message (key,value) metadata. Each line starts
+ Restore per-message (key,value) metadata. Each line starts
with "#= ", followed by a message id, and a space separated
- list of key=value pairs. pair. Ids, keys and values are
- hex encoded if needed.
+ list of key=value pairs. Ids, keys and values are hex
+ encoded if needed. See **notmuch-properties(7)** for more
+ details.
**tags**
- Output per-message metadata, namely tags. See *format* above
+ Restore per-message metadata, namely tags. See *format* above
for more details.
The default is to restore all available types of data. The
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reply(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
the search terms. The summary includes the thread ID, date,
the number of messages in the thread (both the number
matched and the total number), the authors of the thread and
- the subject.
+ the subject. In the case where a thread contains multiple files for
+ some messages, the total number of files is printed in parentheses
+ (see below for an example).
**threads**
Output the thread IDs of all threads with any message
prefix. The prefix matches messages based on filenames. This
option filters filenames of the matching messages.
+EXAMPLE
+=======
+
+The following shows an example of the summary output format, with one
+message having multiple filenames.
+
+::
+
+ % notmuch search date:today.. and tag:bad-news
+ thread:0000000000063c10 Today [1/1] Some Persun; To the bone (inbox unread)
+ thread:0000000000063c25 Today [1/1(2)] Ann Other; Bears (inbox unread)
+ thread:0000000000063c00 Today [1/1] A Thurd; Bites, stings, sad feelings (inbox unread)
+
EXIT STATUS
===========
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
**notmuch-address(1)**
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search(1)**, **notmuch-search-terms(7)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-tag(1)**
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search(1)**, **notmuch-search-terms(7)**, **notmuch-show(1)**,
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
SEE ALSO
========
-**notmuch-address(1)**, **notmuch-compact(1)**, **notmuch-config(1)**,
-**notmuch-count(1)**, **notmuch-dump(1)**, **notmuch-hooks(5)**,
-**notmuch-insert(1)**, **notmuch-new(1)**, **notmuch-reply(1)**,
-**notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch-address(1)**,
+**notmuch-compact(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-properties(7)**,
+**notmuch-reindex(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
The notmuch website: **https://notmuchmail.org**
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-insert(1)**, **notmuch-new(1)**,
-**notmuch-reply(1)**, **notmuch-restore(1)**, **notmuch-search(1)**,
-**notmuch-search-terms(7)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+**notmuch-search-terms(7)**,
+**notmuch-show(1)**,
+**notmuch-tag(1)**
--- /dev/null
+==================
+notmuch-properties
+==================
+
+SYNOPSIS
+========
+
+**notmuch** **count** **property:**\ <*key*>=<*value*>
+
+**notmuch** **search** **property:**\ <*key*>=<*value*>
+
+**notmuch** **show** **property:**\ <*key*>=<*value*>
+
+**notmuch** **reindex** **property:**\ <*key*>=<*value*>
+
+**notmuch** **tag** +<*tag*> **property:**\ <*key*>=<*value*>
+
+
+**notmuch** **dump** **--include=properties**
+
+**notmuch** **restore** **--include=properties**
+
+DESCRIPTION
+===========
+
+Several notmuch commands can search for, modify, add or remove
+properties associated with specific messages. Properties are
+key/value pairs, and a message can have more than one key/value pair
+for the same key.
+
+While users can select based on a specific property in their search
+terms with the prefix **property:**, the notmuch command-line
+interface does not provide mechanisms for modifying properties
+directly to the user.
+
+Instead, message properties are expected to be set and used
+programmatically, according to logic in notmuch itself, or in
+extensions to it.
+
+Extensions to notmuch which make use of properties are encouraged to
+report the specific properties used to the upstream notmuch project,
+as a way of avoiding collisions in the property namespace.
+
+CONVENTIONS
+===========
+
+Any property with a key that starts with "index." will be removed (and
+possibly re-set) upon reindexing (see **notmuch-reindex(1)**).
+
+MESSAGE PROPERTIES
+==================
+
+The following properties are set by notmuch internally in the course
+of its normal activity.
+
+**index.decryption**
+
+ If a message contains encrypted content, and notmuch tries to
+ decrypt that content during indexing, it will add the property
+ ``index.decryption=success`` when the cleartext was successfully
+ indexed. If notmuch attempts to decrypt any part of a message
+ during indexing and that decryption attempt fails, it will add the
+ property ``index.decryption=failure`` to the message.
+
+ Note that it's possible for a single message to have both
+ ``index.decryption=success`` and ``index.decryption=failure``.
+ Consider an encrypted e-mail message that contains another
+ encrypted e-mail message as an attachment -- if the outer message
+ can be decrypted, but the attached part cannot, then both
+ properties will be set on the message as a whole.
+
+ If notmuch never tried to decrypt an encrypted message during
+ indexing (which is the default, see ``index.try_decrypt`` in
+ **notmuch-config(1)**), then this property will not be set on that
+ message.
+
+SEE ALSO
+========
+
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-dump(1)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reindex(1)**,
+**notmuch-restore(1)**,
+***notmuch-search-terms(7)**
**notmuch** **dump** [--format=(batch-tag|sup)] [--] [--output=<*file*>] [--] [<*search-term*> ...]
+**notmuch** **reindex** [option ...] <*search-term*> ...
+
**notmuch** **search** [option ...] <*search-term*> ...
**notmuch** **show** [option ...] <*search-term*> ...
The **property:** prefix searches for messages with a particular
<key>=<value> property pair. Properties are used internally by notmuch
(and extensions) to add metadata to messages. A given key can be
-present on a given message with several different values.
+present on a given message with several different values. See
+**notmuch-properties(7)** for more details.
Operators
---------
Xapian (and hence notmuch) prefixes are either **boolean**, supporting
exact matches like "tag:inbox" or **probabilistic**, supporting a more
flexible **term** based searching. Certain **special** prefixes are
-processed by notmuch in a way not stricly fitting either of Xapian's
+processed by notmuch in a way not strictly fitting either of Xapian's
built in styles. The prefixes currently supported by notmuch are as
follows.
**phrases**. Phrases are indicated by double quotes (but beware you
probably need to protect those from your shell) and insist that those
unstemmed words occur in that order. One useful, but initially
-surprising feature is that the following are equivalant ways to write
+surprising feature is that the following are equivalent ways to write
the same phrase.
- "a list of words"
- a.list.of.words
Both parenthesised lists of terms and quoted phrases are ok with
-probabilisitic prefixes such as **to:**, **from:**, and **subject:**. In particular
+probabilistic prefixes such as **to:**, **from:**, and **subject:**. In particular
::
SEE ALSO
========
-**notmuch(1)**, **notmuch-config(1)**, **notmuch-count(1)**,
-**notmuch-dump(1)**, **notmuch-hooks(5)**, **notmuch-insert(1)**,
-**notmuch-new(1)**, **notmuch-reply(1)**, **notmuch-restore(1)**,
-**notmuch-search(1)**, **notmuch-show(1)**, **notmuch-tag(1)**
+**notmuch(1)**,
+**notmuch-config(1)**,
+**notmuch-count(1)**,
+**notmuch-dump(1)**,
+**notmuch-hooks(5)**,
+**notmuch-insert(1)**,
+**notmuch-new(1)**,
+**notmuch-reindex(1)**,
+**notmuch-properties(1)**,
+***notmuch-reply(1)**,
+**notmuch-restore(1)**,
+**notmuch-search(1)**,
+***notmuch-show(1)**,
+**notmuch-tag(1)**
-.eldeps*
-*.elc
-notmuch-version.el
-notmuch-pkg.el
+/.eldeps*
+/*.elc
+/notmuch-version.el
+/notmuch-pkg.el
#'notmuch-address-expand-name)))
(when setup-company
(notmuch-company-setup))
- (unless (memq pair message-completion-alist)
+ (unless (member pair message-completion-alist)
(setq message-completion-alist
(push pair message-completion-alist)))))
(error "%s" (concat msg (when extra
" (see *Notmuch errors* for more details)"))))
-(defun notmuch-check-async-exit-status (proc msg &optional command err-file)
+(defun notmuch-check-async-exit-status (proc msg &optional command err)
"If PROC exited abnormally, pop up an error buffer and signal an error.
This is a wrapper around `notmuch-check-exit-status' for
asynchronous process sentinels. PROC and MSG must be the
-arguments passed to the sentinel. COMMAND and ERR-FILE, if
-provided, are passed to `notmuch-check-exit-status'. If COMMAND
-is not provided, it is taken from `process-command'."
+arguments passed to the sentinel. COMMAND and ERR, if provided,
+are passed to `notmuch-check-exit-status'. If COMMAND is not
+provided, it is taken from `process-command'."
(let ((exit-status
(case (process-status proc)
((exit) (process-exit-status proc))
((signal) msg))))
(when exit-status
(notmuch-check-exit-status exit-status (or command (process-command proc))
- nil err-file))))
+ nil err))))
-(defun notmuch-check-exit-status (exit-status command &optional output err-file)
+(defun notmuch-check-exit-status (exit-status command &optional output err)
"If EXIT-STATUS is non-zero, pop up an error buffer and signal an error.
If EXIT-STATUS is non-zero, pop up a notmuch error buffer
string describing the signal that terminated the process (such as
returned by `call-process'). COMMAND must be a list giving the
command and its arguments. OUTPUT, if provided, is a string
-giving the output of command. ERR-FILE, if provided, is the name
-of a file containing the error output of command. OUTPUT and the
-contents of ERR-FILE will be included in the error message."
+giving the output of command. ERR, if provided, is the error
+output of command. OUTPUT and ERR will be included in the error
+message."
(cond
((eq exit-status 0) t)
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* ((err (when err-file
- (with-temp-buffer
- (insert-file-contents err-file)
- (unless (eobp)
- (buffer-string)))))
- (command-string
+ (let* ((command-string
(mapconcat (lambda (arg)
(shell-quote-argument
(cond ((stringp arg) arg)
(with-temp-buffer
(let ((err-file (make-temp-file "nmerr")))
(unwind-protect
- (let ((status (notmuch-call-notmuch--helper (list t err-file) args)))
+ (let ((status (notmuch-call-notmuch--helper (list t err-file) args))
+ (err (with-temp-buffer
+ (insert-file-contents err-file)
+ (unless (eobp)
+ (buffer-string)))))
(notmuch-check-exit-status status (cons notmuch-command args)
- (buffer-string) err-file)
+ (buffer-string) err)
(goto-char (point-min))
(read (current-buffer)))
(delete-file err-file)))))
as that will interfere with the handling of stderr and the exit
status."
- ;; There is no way (as of Emacs 24.3) 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.
- (let* ((err-file (make-temp-file "nmerr"))
- ;; Use a pipe
- (process-connection-type nil)
- ;; Find notmuch using Emacs' `exec-path'
- (command (or (executable-find notmuch-command)
- (error "command not found: %s" notmuch-command)))
- (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 (err-file err-buffer 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))
+ (process-put proc 'err-buffer err-buffer)
+ ;; Silence "Process NAME stderr finished" in stderr by adding a
+ ;; no-op sentinel to the fake stderr process object
+ (set-process-sentinel (get-buffer-process err-buffer) #'ignore))
+
+ ;; 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))
+
(process-put proc 'sub-sentinel sentinel)
(process-put proc 'real-command (cons notmuch-command args))
(set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
proc))
(defun notmuch-start-notmuch-sentinel (proc event)
- (let ((err-file (process-get proc 'err-file))
- (sub-sentinel (process-get proc 'sub-sentinel))
- (real-command (process-get proc 'real-command)))
+ "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 (when (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)))
(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-file))
+ (notmuch-check-async-exit-status proc event real-command err))
;; If that didn't signal an error, then any error output was
;; really warning output. Show warnings, if any.
(let ((warnings
- (with-temp-buffer
- (unless (= (second (insert-file-contents err-file)) 0)
+ (when err
+ (with-current-buffer err-buffer
+ (goto-char (point-min))
(end-of-line)
;; Show first line; stuff remaining lines in the
;; errors buffer.
;; Emacs behaves strangely if an error escapes from a sentinel,
;; so turn errors into messages.
(message "%s" (error-message-string err))))
- (ignore-errors (delete-file err-file))))
+ (when err-buffer (kill-buffer err-buffer))
+ (when err-file (ignore-errors (delete-file err-file)))))
;; This variable is used only buffer local, but it needs to be
;; declared globally first to avoid compiler warnings.
else
collect pair)))
(notmuch-mua-mail (plist-get reply-headers :To)
- (plist-get reply-headers :Subject)
+ (notmuch-sanitize (plist-get reply-headers :Subject))
(notmuch-headers-plist-to-alist reply-headers)
nil (notmuch-mua-get-switch-function))))
;; aren't wiped out.
(setq notmuch-show-thread-id thread-id
notmuch-show-parent-buffer parent-buffer
- notmuch-show-query-context query-context
+ notmuch-show-query-context (if (or (string= query-context "")
+ (string= query-context "*"))
+ nil query-context)
notmuch-show-process-crypto notmuch-crypto-process-mime
;; If `elide-toggle', invert the default value.
TAG-CHANGES is a list of strings of the form \"+tag\" or
\"-tag\" to add or remove tags, respectively.
-Note: Other code should always use this function alter tags of
+Note: Other code should always use this function to alter tags of
messages instead of running (notmuch-call-notmuch-process \"tag\" ..)
directly, so that hooks specified in notmuch-before-tag-hook and
notmuch-after-tag-hook will be run."
(defun notmuch-tree-refresh-view ()
"Refresh view."
(interactive)
+ (when (get-buffer-process (current-buffer))
+ (error "notmuch tree process already running for current buffer"))
(let ((inhibit-read-only t)
(basic-query notmuch-tree-basic-query)
(query-context notmuch-tree-query-context)
(notmuch-tree-mode)
(add-hook 'post-command-hook #'notmuch-tree-command-hook t t)
(setq notmuch-tree-basic-query basic-query)
- (setq notmuch-tree-query-context query-context)
+ (setq notmuch-tree-query-context (if (or (string= query-context "")
+ (string= query-context "*"))
+ nil query-context))
(setq notmuch-tree-target-msg target)
(setq notmuch-tree-open-target open-target)
;; Set the default value for `notmuch-show-process-crypto' in this
;; Have fun, and let us know if you have any comment, questions, or
;; kudos: Notmuch list <notmuch@notmuchmail.org> (subscription is not
;; required, but is available from https://notmuchmail.org).
-
+;;
+;; Note for MELPA users (and others tracking the development version
+;; of notmuch-emacs):
+;;
+;; This emacs package needs a fairly closely matched version of the
+;; notmuch program. If you use the MELPA version of notmuch.el (as
+;; opposed to MELPA stable), you should be prepared to track the
+;; master development branch (i.e. build from git) for the notmuch
+;; program as well. Upgrading notmuch-emacs too far beyond the notmuch
+;; program can CAUSE YOUR EMAIL TO STOP WORKING.
+;;
+;; TL;DR: notmuch-emacs from MELPA and notmuch from distro packages is
+;; NOT SUPPORTED.
+;;
;;; Code:
(eval-when-compile (require 'cl))
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
+#include <stdbool.h>
+
#include "gmime-filter-reply.h"
/**
g_mime_filter_reply_init (GMimeFilterReply *filter, GMimeFilterReplyClass *klass)
{
(void) klass;
- filter->saw_nl = TRUE;
- filter->saw_angle = FALSE;
+ filter->saw_nl = true;
+ filter->saw_angle = false;
}
static void
(void) prespace;
if (reply->encode) {
- g_mime_filter_set_size (filter, 3 * inlen, FALSE);
+ g_mime_filter_set_size (filter, 3 * inlen, false);
outptr = filter->outbuf;
while (inptr < inend) {
if (reply->saw_nl) {
*outptr++ = '>';
*outptr++ = ' ';
- reply->saw_nl = FALSE;
+ reply->saw_nl = false;
}
if (*inptr == '\n')
- reply->saw_nl = TRUE;
+ reply->saw_nl = true;
else
- reply->saw_nl = FALSE;
+ reply->saw_nl = false;
if (*inptr != '\r')
*outptr++ = *inptr;
inptr++;
}
} else {
- g_mime_filter_set_size (filter, inlen + 1, FALSE);
+ g_mime_filter_set_size (filter, inlen + 1, false);
outptr = filter->outbuf;
while (inptr < inend) {
if (reply->saw_nl) {
if (*inptr == '>')
- reply->saw_angle = TRUE;
+ reply->saw_angle = true;
else
*outptr++ = *inptr;
- reply->saw_nl = FALSE;
+ reply->saw_nl = false;
} else if (reply->saw_angle) {
if (*inptr == ' ')
;
else
*outptr++ = *inptr;
- reply->saw_angle = FALSE;
+ reply->saw_angle = false;
} else if (*inptr != '\r') {
if (*inptr == '\n')
- reply->saw_nl = TRUE;
+ reply->saw_nl = true;
*outptr++ = *inptr;
}
{
GMimeFilterReply *reply = (GMimeFilterReply *) filter;
- reply->saw_nl = TRUE;
- reply->saw_angle = FALSE;
+ reply->saw_nl = true;
+ reply->saw_angle = false;
}
/**
* g_mime_filter_reply_new:
- * @encode: %TRUE if the filter should encode or %FALSE otherwise
+ * @encode: %true if the filter should encode or %false otherwise
* @dots: encode/decode dots (as for SMTP)
*
* Creates a new #GMimeFilterReply filter.
*
- * If @encode is %TRUE, then all lines will be prefixed by "> ",
+ * If @encode is %true, then all lines will be prefixed by "> ",
* otherwise any lines starting with "> " will have that removed
*
* Returns: a new #GMimeFilterReply filter.
{
GMimeFilterReply *new_reply;
- new_reply = (GMimeFilterReply *) g_object_newv (GMIME_TYPE_FILTER_REPLY, 0, NULL);
+ new_reply = (GMimeFilterReply *) g_object_new (GMIME_TYPE_FILTER_REPLY, NULL);
new_reply->encode = encode;
return (GMimeFilter *) new_reply;
LINKER_NAME = libnotmuch.$(LIBRARY_SUFFIX)
SONAME = $(LINKER_NAME).$(LIBNOTMUCH_VERSION_MAJOR)
LIBNAME = $(SONAME).$(LIBNOTMUCH_VERSION_MINOR).$(LIBNOTMUCH_VERSION_RELEASE)
-LIBRARY_LINK_FLAG = -shared -Wl,--version-script=$(lib)/notmuch.sym,-soname=$(SONAME) $(NO_UNDEFINED_LDFLAGS)
+LIBRARY_LINK_FLAG = -shared -Wl,--version-script=$(srcdir)/$(lib)/notmuch.sym,-soname=$(SONAME) $(NO_UNDEFINED_LDFLAGS)
ifeq ($(PLATFORM),OPENBSD)
LIBRARY_LINK_FLAG += -lc
endif
$(dir)/filenames.c \
$(dir)/string-list.c \
$(dir)/message-file.c \
+ $(dir)/message-id.c \
$(dir)/messages.c \
$(dir)/sha1.c \
$(dir)/built-with.c \
$(dir)/string-map.c \
+ $(dir)/indexopts.c \
$(dir)/tags.c
libnotmuch_cxx_srcs = \
$(dir)/directory.cc \
$(dir)/index.cc \
$(dir)/message.cc \
+ $(dir)/add-message.cc \
$(dir)/message-property.cc \
$(dir)/query.cc \
$(dir)/query-fp.cc \
--- /dev/null
+#include "database-private.h"
+
+/* Parse a References header value, putting a (talloc'ed under 'ctx')
+ * copy of each referenced message-id into 'hash'.
+ *
+ * We explicitly avoid including any reference identical to
+ * 'message_id' in the result (to avoid mass confusion when a single
+ * message references itself cyclically---and yes, mail messages are
+ * not infrequent in the wild that do this---don't ask me why).
+ *
+ * Return the last reference parsed, if it is not equal to message_id.
+ */
+static char *
+parse_references (void *ctx,
+ const char *message_id,
+ GHashTable *hash,
+ const char *refs)
+{
+ char *ref, *last_ref = NULL;
+
+ if (refs == NULL || *refs == '\0')
+ return NULL;
+
+ while (*refs) {
+ ref = _notmuch_message_id_parse (ctx, refs, &refs);
+
+ if (ref && strcmp (ref, message_id)) {
+ g_hash_table_add (hash, ref);
+ last_ref = ref;
+ }
+ }
+
+ /* The return value of this function is used to add a parent
+ * reference to the database. We should avoid making a message
+ * its own parent, thus the above check.
+ */
+ return talloc_strdup(ctx, last_ref);
+}
+
+static const char *
+_notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
+{
+ /* 16 bytes (+ terminator) for hexadecimal representation of
+ * a 64-bit integer. */
+ static char thread_id[17];
+ Xapian::WritableDatabase *db;
+
+ db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+ notmuch->last_thread_id++;
+
+ sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id);
+
+ db->set_metadata ("last_thread_id", thread_id);
+
+ return thread_id;
+}
+
+static char *
+_get_metadata_thread_id_key (void *ctx, const char *message_id)
+{
+ if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
+ message_id = _notmuch_message_id_compressed (ctx, message_id);
+
+ return talloc_asprintf (ctx, NOTMUCH_METADATA_THREAD_ID_PREFIX "%s",
+ message_id);
+}
+
+
+static notmuch_status_t
+_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
+ void *ctx,
+ const char *message_id,
+ const char **thread_id_ret);
+
+
+/* Find the thread ID to which the message with 'message_id' belongs.
+ *
+ * Note: 'thread_id_ret' must not be NULL!
+ * On success '*thread_id_ret' is set to a newly talloced string belonging to
+ * 'ctx'.
+ *
+ * Note: If there is no message in the database with the given
+ * 'message_id' then a new thread_id will be allocated for this
+ * message ID and stored in the database metadata so that the
+ * thread ID can be looked up if the message is added to the database
+ * later.
+ */
+static notmuch_status_t
+_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
+ void *ctx,
+ const char *message_id,
+ const char **thread_id_ret)
+{
+ notmuch_private_status_t status;
+ notmuch_message_t *message;
+
+ if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS))
+ return _resolve_message_id_to_thread_id_old (notmuch, ctx, message_id,
+ thread_id_ret);
+
+ /* Look for this message (regular or ghost) */
+ message = _notmuch_message_create_for_message_id (
+ notmuch, message_id, &status);
+ if (status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
+ /* Message exists */
+ *thread_id_ret = talloc_steal (
+ ctx, notmuch_message_get_thread_id (message));
+ } else if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
+ /* Message did not exist. Give it a fresh thread ID and
+ * populate this message as a ghost message. */
+ *thread_id_ret = talloc_strdup (
+ ctx, _notmuch_database_generate_thread_id (notmuch));
+ if (! *thread_id_ret) {
+ status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
+ } else {
+ status = _notmuch_message_initialize_ghost (message, *thread_id_ret);
+ if (status == 0)
+ /* Commit the new ghost message */
+ _notmuch_message_sync (message);
+ }
+ } else {
+ /* Create failed. Fall through. */
+ }
+
+ notmuch_message_destroy (message);
+
+ return COERCE_STATUS (status, "Error creating ghost message");
+}
+
+/* Pre-ghost messages _resolve_message_id_to_thread_id */
+static notmuch_status_t
+_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
+ void *ctx,
+ const char *message_id,
+ const char **thread_id_ret)
+{
+ notmuch_status_t status;
+ notmuch_message_t *message;
+ std::string thread_id_string;
+ char *metadata_key;
+ Xapian::WritableDatabase *db;
+
+ status = notmuch_database_find_message (notmuch, message_id, &message);
+
+ if (status)
+ return status;
+
+ if (message) {
+ *thread_id_ret = talloc_steal (ctx,
+ notmuch_message_get_thread_id (message));
+
+ notmuch_message_destroy (message);
+
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ /* Message has not been seen yet.
+ *
+ * We may have seen a reference to it already, in which case, we
+ * can return the thread ID stored in the metadata. Otherwise, we
+ * generate a new thread ID and store it there.
+ */
+ db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+ metadata_key = _get_metadata_thread_id_key (ctx, message_id);
+ thread_id_string = notmuch->xapian_db->get_metadata (metadata_key);
+
+ if (thread_id_string.empty()) {
+ *thread_id_ret = talloc_strdup (ctx,
+ _notmuch_database_generate_thread_id (notmuch));
+ db->set_metadata (metadata_key, *thread_id_ret);
+ } else {
+ *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str());
+ }
+
+ talloc_free (metadata_key);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+_merge_threads (notmuch_database_t *notmuch,
+ const char *winner_thread_id,
+ const char *loser_thread_id)
+{
+ Xapian::PostingIterator loser, loser_end;
+ notmuch_message_t *message = NULL;
+ notmuch_private_status_t private_status;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+ _notmuch_database_find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
+
+ for ( ; loser != loser_end; loser++) {
+ message = _notmuch_message_create (notmuch, notmuch,
+ *loser, &private_status);
+ if (message == NULL) {
+ ret = COERCE_STATUS (private_status,
+ "Cannot find document for doc_id from query");
+ goto DONE;
+ }
+
+ _notmuch_message_remove_term (message, "thread", loser_thread_id);
+ _notmuch_message_add_term (message, "thread", winner_thread_id);
+ _notmuch_message_sync (message);
+
+ notmuch_message_destroy (message);
+ message = NULL;
+ }
+
+ DONE:
+ if (message)
+ notmuch_message_destroy (message);
+
+ return ret;
+}
+
+static void
+_my_talloc_free_for_g_hash (void *ptr)
+{
+ talloc_free (ptr);
+}
+
+notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+ notmuch_message_t *message,
+ notmuch_message_file_t *message_file,
+ const char **thread_id)
+{
+ GHashTable *parents = NULL;
+ const char *refs, *in_reply_to, *in_reply_to_message_id;
+ const char *last_ref_message_id, *this_message_id;
+ GList *l, *keys = NULL;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+ parents = g_hash_table_new_full (g_str_hash, g_str_equal,
+ _my_talloc_free_for_g_hash, NULL);
+ this_message_id = notmuch_message_get_message_id (message);
+
+ refs = _notmuch_message_file_get_header (message_file, "references");
+ last_ref_message_id = parse_references (message,
+ this_message_id,
+ parents, refs);
+
+ in_reply_to = _notmuch_message_file_get_header (message_file, "in-reply-to");
+ in_reply_to_message_id = parse_references (message,
+ this_message_id,
+ parents, in_reply_to);
+
+ /* For the parent of this message, use the last message ID of the
+ * References header, if available. If not, fall back to the
+ * first message ID in the In-Reply-To header. */
+ if (last_ref_message_id) {
+ _notmuch_message_add_term (message, "replyto",
+ last_ref_message_id);
+ } else if (in_reply_to_message_id) {
+ _notmuch_message_add_term (message, "replyto",
+ in_reply_to_message_id);
+ }
+
+ keys = g_hash_table_get_keys (parents);
+ for (l = keys; l; l = l->next) {
+ char *parent_message_id;
+ const char *parent_thread_id = NULL;
+
+ parent_message_id = (char *) l->data;
+
+ _notmuch_message_add_term (message, "reference",
+ parent_message_id);
+
+ ret = _resolve_message_id_to_thread_id (notmuch,
+ message,
+ parent_message_id,
+ &parent_thread_id);
+ if (ret)
+ goto DONE;
+
+ if (*thread_id == NULL) {
+ *thread_id = talloc_strdup (message, parent_thread_id);
+ _notmuch_message_add_term (message, "thread", *thread_id);
+ } else if (strcmp (*thread_id, parent_thread_id)) {
+ ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
+ if (ret)
+ goto DONE;
+ }
+ }
+
+ DONE:
+ if (keys)
+ g_list_free (keys);
+ if (parents)
+ g_hash_table_unref (parents);
+
+ return ret;
+}
+
+static notmuch_status_t
+_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
+ notmuch_message_t *message,
+ const char **thread_id)
+{
+ const char *message_id = notmuch_message_get_message_id (message);
+ Xapian::PostingIterator child, children_end;
+ notmuch_message_t *child_message = NULL;
+ const char *child_thread_id;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ notmuch_private_status_t private_status;
+
+ _notmuch_database_find_doc_ids (notmuch, "reference", message_id, &child, &children_end);
+
+ for ( ; child != children_end; child++) {
+
+ child_message = _notmuch_message_create (message, notmuch,
+ *child, &private_status);
+ if (child_message == NULL) {
+ ret = COERCE_STATUS (private_status,
+ "Cannot find document for doc_id from query");
+ goto DONE;
+ }
+
+ child_thread_id = notmuch_message_get_thread_id (child_message);
+ if (*thread_id == NULL) {
+ *thread_id = talloc_strdup (message, child_thread_id);
+ _notmuch_message_add_term (message, "thread", *thread_id);
+ } else if (strcmp (*thread_id, child_thread_id)) {
+ _notmuch_message_remove_term (child_message, "reference",
+ message_id);
+ _notmuch_message_sync (child_message);
+ ret = _merge_threads (notmuch, *thread_id, child_thread_id);
+ if (ret)
+ goto DONE;
+ }
+
+ notmuch_message_destroy (child_message);
+ child_message = NULL;
+ }
+
+ DONE:
+ if (child_message)
+ notmuch_message_destroy (child_message);
+
+ return ret;
+}
+
+/* Fetch and clear the stored thread_id for message, or NULL if none. */
+static char *
+_consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
+ notmuch_message_t *message)
+{
+ const char *message_id;
+ std::string stored_id;
+ char *metadata_key;
+
+ message_id = notmuch_message_get_message_id (message);
+ metadata_key = _get_metadata_thread_id_key (ctx, message_id);
+
+ /* Check if we have already seen related messages to this one.
+ * If we have then use the thread_id that we stored at that time.
+ */
+ stored_id = notmuch->xapian_db->get_metadata (metadata_key);
+ if (stored_id.empty ()) {
+ return NULL;
+ } else {
+ Xapian::WritableDatabase *db;
+
+ db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+
+ /* Clear the metadata for this message ID. We don't need it
+ * anymore. */
+ db->set_metadata (metadata_key, "");
+
+ return talloc_strdup (ctx, stored_id.c_str ());
+ }
+}
+
+/* Given a blank or ghost 'message' and its corresponding
+ * 'message_file' link it to existing threads in the database.
+ *
+ * First, if is_ghost, this retrieves the thread ID already stored in
+ * the message (which will be the case if a message was previously
+ * added that referenced this one). If the message is blank
+ * (!is_ghost), it doesn't have a thread ID yet (we'll generate one
+ * later in this function). If the database does not support ghost
+ * messages, this checks for a thread ID stored in database metadata
+ * for this message ID.
+ *
+ * Second, we look at 'message_file' and its link-relevant headers
+ * (References and In-Reply-To) for message IDs.
+ *
+ * Finally, we look in the database for existing message that
+ * reference 'message'.
+ *
+ * In all cases, we assign to the current message the first thread ID
+ * found. We will also merge any existing, distinct threads where this
+ * message belongs to both, (which is not uncommon when messages are
+ * processed out of order).
+ *
+ * Finally, if no thread ID has been found through referenced messages, we
+ * call _notmuch_message_generate_thread_id to generate a new thread
+ * ID. This should only happen for new, top-level messages, (no
+ * References or In-Reply-To header in this message, and no previously
+ * added message refers to this message).
+ */
+static notmuch_status_t
+_notmuch_database_link_message (notmuch_database_t *notmuch,
+ notmuch_message_t *message,
+ notmuch_message_file_t *message_file,
+ bool is_ghost)
+{
+ void *local = talloc_new (NULL);
+ notmuch_status_t status;
+ const char *thread_id = NULL;
+
+ /* Check if the message already had a thread ID */
+ if (notmuch->features & NOTMUCH_FEATURE_GHOSTS) {
+ if (is_ghost)
+ thread_id = notmuch_message_get_thread_id (message);
+ } else {
+ thread_id = _consume_metadata_thread_id (local, notmuch, message);
+ if (thread_id)
+ _notmuch_message_add_term (message, "thread", thread_id);
+ }
+
+ status = _notmuch_database_link_message_to_parents (notmuch, message,
+ message_file,
+ &thread_id);
+ if (status)
+ goto DONE;
+
+ if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS)) {
+ /* In general, it shouldn't be necessary to link children,
+ * since the earlier indexing of those children will have
+ * stored a thread ID for the missing parent. However, prior
+ * to ghost messages, these stored thread IDs were NOT
+ * rewritten during thread merging (and there was no
+ * performant way to do so), so if indexed children were
+ * pulled into a different thread ID by a merge, it was
+ * necessary to pull them *back* into the stored thread ID of
+ * the parent. With ghost messages, we just rewrite the
+ * stored thread IDs during merging, so this workaround isn't
+ * necessary. */
+ status = _notmuch_database_link_message_to_children (notmuch, message,
+ &thread_id);
+ if (status)
+ goto DONE;
+ }
+
+ /* If not part of any existing thread, generate a new thread ID. */
+ if (thread_id == NULL) {
+ thread_id = _notmuch_database_generate_thread_id (notmuch);
+
+ _notmuch_message_add_term (message, "thread", thread_id);
+ }
+
+ DONE:
+ talloc_free (local);
+
+ return status;
+}
+
+notmuch_status_t
+notmuch_database_index_file (notmuch_database_t *notmuch,
+ const char *filename,
+ notmuch_indexopts_t *indexopts,
+ notmuch_message_t **message_ret)
+{
+ notmuch_message_file_t *message_file;
+ notmuch_message_t *message = NULL;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2;
+ notmuch_private_status_t private_status;
+ bool is_ghost = false, is_new = false;
+ notmuch_indexopts_t *def_indexopts = NULL;
+
+ const char *date;
+ const char *from, *to, *subject;
+ char *message_id = NULL;
+
+ if (message_ret)
+ *message_ret = NULL;
+
+ ret = _notmuch_database_ensure_writable (notmuch);
+ if (ret)
+ return ret;
+
+ message_file = _notmuch_message_file_open (notmuch, filename);
+ if (message_file == NULL)
+ return NOTMUCH_STATUS_FILE_ERROR;
+
+ /* Adding a message may change many documents. Do this all
+ * atomically. */
+ ret = notmuch_database_begin_atomic (notmuch);
+ if (ret)
+ goto DONE;
+
+ ret = _notmuch_message_file_get_headers (message_file,
+ &from, &subject, &to, &date,
+ &message_id);
+ if (ret)
+ goto DONE;
+
+ try {
+ /* Now that we have a message ID, we get a message object,
+ * (which may or may not reference an existing document in the
+ * database). */
+
+ message = _notmuch_message_create_for_message_id (notmuch,
+ message_id,
+ &private_status);
+
+ talloc_free (message_id);
+
+ /* We cannot call notmuch_message_get_flag for a new message */
+ switch (private_status) {
+ case NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND:
+ is_ghost = false;
+ is_new = true;
+ break;
+ case NOTMUCH_PRIVATE_STATUS_SUCCESS:
+ is_ghost = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_GHOST);
+ is_new = false;
+ break;
+ default:
+ ret = COERCE_STATUS (private_status,
+ "Unexpected status value from _notmuch_message_create_for_message_id");
+ goto DONE;
+ }
+
+ _notmuch_message_add_filename (message, filename);
+
+ if (is_new || is_ghost) {
+ _notmuch_message_add_term (message, "type", "mail");
+ if (is_ghost)
+ /* Convert ghost message to a regular message */
+ _notmuch_message_remove_term (message, "type", "ghost");
+ }
+
+ ret = _notmuch_database_link_message (notmuch, message,
+ message_file, is_ghost);
+ if (ret)
+ goto DONE;
+
+ if (is_new || is_ghost)
+ _notmuch_message_set_header_values (message, date, from, subject);
+
+ if (!indexopts) {
+ def_indexopts = notmuch_database_get_default_indexopts (notmuch);
+ indexopts = def_indexopts;
+ }
+
+ ret = _notmuch_message_index_file (message, indexopts, message_file);
+ if (ret)
+ goto DONE;
+
+ if (! is_new && !is_ghost)
+ ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
+
+ _notmuch_message_sync (message);
+ } catch (const Xapian::Error &error) {
+ _notmuch_database_log (notmuch, "A Xapian exception occurred adding message: %s.\n",
+ error.get_msg().c_str());
+ notmuch->exception_reported = true;
+ ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
+ goto DONE;
+ }
+
+ DONE:
+ if (def_indexopts)
+ notmuch_indexopts_destroy (def_indexopts);
+
+ if (message) {
+ if ((ret == NOTMUCH_STATUS_SUCCESS ||
+ ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) && message_ret)
+ *message_ret = message;
+ else
+ notmuch_message_destroy (message);
+ }
+
+ if (message_file)
+ _notmuch_message_file_close (message_file);
+
+ ret2 = notmuch_database_end_atomic (notmuch);
+ if ((ret == NOTMUCH_STATUS_SUCCESS ||
+ ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) &&
+ ret2 != NOTMUCH_STATUS_SUCCESS)
+ ret = ret2;
+
+ return ret;
+}
+
+notmuch_status_t
+notmuch_database_add_message (notmuch_database_t *notmuch,
+ const char *filename,
+ notmuch_message_t **message_ret)
+{
+ return notmuch_database_index_file (notmuch, filename,
+ NULL,
+ message_ret);
+
+}
} else if (STRNCMP_LITERAL (name, "retry_lock") == 0) {
return HAVE_XAPIAN_DB_RETRY_LOCK;
} else {
- return FALSE;
+ return false;
}
}
db->set_metadata (CONFIG_PREFIX + key, value);
} catch (const Xapian::Error &error) {
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
_notmuch_database_log (notmuch, "Error: A Xapian exception occurred setting metadata: %s\n",
error.get_msg().c_str());
}
value = notmuch->xapian_db->get_metadata (CONFIG_PREFIX + key);
} catch (const Xapian::Error &error) {
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
_notmuch_database_log (notmuch, "Error: A Xapian exception occurred getting metadata: %s\n",
error.get_msg().c_str());
}
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred getting metadata iterator: %s.\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
notmuch_config_list_valid (notmuch_config_list_t *metadata)
{
if (metadata->iterator == metadata->notmuch->xapian_db->metadata_keys_end ())
- return FALSE;
+ return false;
- return TRUE;
+ return true;
}
const char *
Xapian::QueryParser::FLAG_PURE_NOT)
struct _notmuch_database {
- notmuch_bool_t exception_reported;
+ bool exception_reported;
char *path;
notmuch_database_mode_t mode;
int atomic_nesting;
- /* TRUE if changes have been made in this atomic section */
- notmuch_bool_t atomic_dirty;
+ /* true if changes have been made in this atomic section */
+ bool atomic_dirty;
Xapian::Database *xapian_db;
/* Bit mask of features used by this database. This is a
Xapian::TermIterator &end,
const char *prefix);
+void
+_notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
+ const char *prefix_name,
+ const char *value,
+ Xapian::PostingIterator *begin,
+ Xapian::PostingIterator *end);
#endif
return "Operation requires a database upgrade";
case NOTMUCH_STATUS_PATH_ERROR:
return "Path supplied is illegal for this 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";
default:
case NOTMUCH_STATUS_LAST_STATUS:
return "Unknown error status value";
*end = notmuch->xapian_db->postlist_end (term);
}
-static void
-find_doc_ids (notmuch_database_t *notmuch,
- const char *prefix_name,
- const char *value,
- Xapian::PostingIterator *begin,
- Xapian::PostingIterator *end)
+void
+_notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
+ const char *prefix_name,
+ const char *value,
+ Xapian::PostingIterator *begin,
+ Xapian::PostingIterator *end)
{
char *term;
{
Xapian::PostingIterator i, end;
- find_doc_ids (notmuch, prefix_name, value, &i, &end);
+ _notmuch_database_find_doc_ids (notmuch, prefix_name, value, &i, &end);
if (i == end) {
*doc_id = 0;
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
*message_ret = NULL;
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
}
-/* Advance 'str' past any whitespace or RFC 822 comments. A comment is
- * a (potentially nested) parenthesized sequence with '\' used to
- * escape any character (including parentheses).
- *
- * If the sequence to be skipped continues to the end of the string,
- * then 'str' will be left pointing at the final terminating '\0'
- * character.
- */
-static void
-skip_space_and_comments (const char **str)
-{
- const char *s;
-
- s = *str;
- while (*s && (isspace (*s) || *s == '(')) {
- while (*s && isspace (*s))
- s++;
- if (*s == '(') {
- int nesting = 1;
- s++;
- while (*s && nesting) {
- if (*s == '(') {
- nesting++;
- } else if (*s == ')') {
- nesting--;
- } else if (*s == '\\') {
- if (*(s+1))
- s++;
- }
- s++;
- }
- }
- }
-
- *str = s;
-}
-
-/* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
- * comments, and the '<' and '>' delimiters.
- *
- * If not NULL, then *next will be made to point to the first character
- * not parsed, (possibly pointing to the final '\0' terminator.
- *
- * Returns a newly talloc'ed string belonging to 'ctx'.
- *
- * Returns NULL if there is any error parsing the message-id. */
-static char *
-_parse_message_id (void *ctx, const char *message_id, const char **next)
-{
- const char *s, *end;
- char *result;
-
- if (message_id == NULL || *message_id == '\0')
- return NULL;
-
- s = message_id;
-
- skip_space_and_comments (&s);
-
- /* Skip any unstructured text as well. */
- while (*s && *s != '<')
- s++;
-
- if (*s == '<') {
- s++;
- } else {
- if (next)
- *next = s;
- return NULL;
- }
-
- skip_space_and_comments (&s);
-
- end = s;
- while (*end && *end != '>')
- end++;
- if (next) {
- if (*end)
- *next = end + 1;
- else
- *next = end;
- }
-
- if (end > s && *end == '>')
- end--;
- if (end <= s)
- return NULL;
-
- result = talloc_strndup (ctx, s, end - s + 1);
-
- /* Finally, collapse any whitespace that is within the message-id
- * itself. */
- {
- char *r;
- int len;
-
- for (r = result, len = strlen (r); *r; r++, len--)
- if (*r == ' ' || *r == '\t')
- memmove (r, r+1, len);
- }
-
- return result;
-}
-
-/* Parse a References header value, putting a (talloc'ed under 'ctx')
- * copy of each referenced message-id into 'hash'.
- *
- * We explicitly avoid including any reference identical to
- * 'message_id' in the result (to avoid mass confusion when a single
- * message references itself cyclically---and yes, mail messages are
- * not infrequent in the wild that do this---don't ask me why).
- *
- * Return the last reference parsed, if it is not equal to message_id.
- */
-static char *
-parse_references (void *ctx,
- const char *message_id,
- GHashTable *hash,
- const char *refs)
-{
- char *ref, *last_ref = NULL;
-
- if (refs == NULL || *refs == '\0')
- return NULL;
-
- while (*refs) {
- ref = _parse_message_id (ctx, refs, &refs);
-
- if (ref && strcmp (ref, message_id)) {
- g_hash_table_add (hash, ref);
- last_ref = ref;
- }
- }
-
- /* The return value of this function is used to add a parent
- * reference to the database. We should avoid making a message
- * its own parent, thus the above check.
- */
- return talloc_strdup(ctx, last_ref);
-}
-
notmuch_status_t
notmuch_database_create (const char *path, notmuch_database_t **database)
{
* committed revision number until we commit the atomic section.
*/
if (notmuch->atomic_nesting)
- notmuch->atomic_dirty = TRUE;
+ notmuch->atomic_dirty = true;
else
notmuch->revision = new_revision;
}
notmuch = talloc_zero (NULL, notmuch_database_t);
- notmuch->exception_reported = FALSE;
+ notmuch->exception_reported = false;
notmuch->status_string = NULL;
notmuch->path = talloc_strdup (notmuch, path);
- if (notmuch->path[strlen (notmuch->path) - 1] == '/')
- notmuch->path[strlen (notmuch->path) - 1] = '\0';
+ strip_trailing(notmuch->path, '/');
notmuch->mode = mode;
notmuch->atomic_nesting = 0;
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;
+ notmuch->exception_reported = true;
}
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
notmuch_database_t *notmuch = NULL;
struct stat statbuf;
- notmuch_bool_t keep_backup;
+ bool keep_backup;
char *message = NULL;
local = talloc_new (NULL);
ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
- keep_backup = FALSE;
+ keep_backup = false;
}
else {
- keep_backup = TRUE;
+ keep_backup = true;
}
if (stat (backup_path, &statbuf) != -1) {
Xapian::WritableDatabase *db;
struct sigaction action;
struct itimerval timerval;
- notmuch_bool_t timer_is_active = FALSE;
+ bool timer_is_active = false;
enum _notmuch_features target_features, new_features;
notmuch_status_t status;
notmuch_private_status_t private_status;
timerval.it_value.tv_usec = 0;
setitimer (ITIMER_REAL, &timerval, NULL);
- timer_is_active = TRUE;
+ timer_is_active = true;
}
/* Figure out how much total work we need to do. */
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
if (notmuch->atomic_dirty) {
++notmuch->revision;
- notmuch->atomic_dirty = FALSE;
+ notmuch->atomic_dirty = false;
}
DONE:
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
return status;
return notmuch->last_doc_id;
}
-static const char *
-_notmuch_database_generate_thread_id (notmuch_database_t *notmuch)
-{
- /* 16 bytes (+ terminator) for hexadecimal representation of
- * a 64-bit integer. */
- static char thread_id[17];
- Xapian::WritableDatabase *db;
-
- db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
- notmuch->last_thread_id++;
-
- sprintf (thread_id, "%016" PRIx64, notmuch->last_thread_id);
-
- db->set_metadata ("last_thread_id", thread_id);
-
- return thread_id;
-}
-
-static char *
-_get_metadata_thread_id_key (void *ctx, const char *message_id)
-{
- if (strlen (message_id) > NOTMUCH_MESSAGE_ID_MAX)
- message_id = _notmuch_message_id_compressed (ctx, message_id);
-
- return talloc_asprintf (ctx, NOTMUCH_METADATA_THREAD_ID_PREFIX "%s",
- message_id);
-}
-
-static notmuch_status_t
-_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
- void *ctx,
- const char *message_id,
- const char **thread_id_ret);
-
-/* Find the thread ID to which the message with 'message_id' belongs.
- *
- * Note: 'thread_id_ret' must not be NULL!
- * On success '*thread_id_ret' is set to a newly talloced string belonging to
- * 'ctx'.
- *
- * Note: If there is no message in the database with the given
- * 'message_id' then a new thread_id will be allocated for this
- * message ID and stored in the database metadata so that the
- * thread ID can be looked up if the message is added to the database
- * later.
- */
-static notmuch_status_t
-_resolve_message_id_to_thread_id (notmuch_database_t *notmuch,
- void *ctx,
- const char *message_id,
- const char **thread_id_ret)
-{
- notmuch_private_status_t status;
- notmuch_message_t *message;
-
- if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS))
- return _resolve_message_id_to_thread_id_old (notmuch, ctx, message_id,
- thread_id_ret);
-
- /* Look for this message (regular or ghost) */
- message = _notmuch_message_create_for_message_id (
- notmuch, message_id, &status);
- if (status == NOTMUCH_PRIVATE_STATUS_SUCCESS) {
- /* Message exists */
- *thread_id_ret = talloc_steal (
- ctx, notmuch_message_get_thread_id (message));
- } else if (status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND) {
- /* Message did not exist. Give it a fresh thread ID and
- * populate this message as a ghost message. */
- *thread_id_ret = talloc_strdup (
- ctx, _notmuch_database_generate_thread_id (notmuch));
- if (! *thread_id_ret) {
- status = NOTMUCH_PRIVATE_STATUS_OUT_OF_MEMORY;
- } else {
- status = _notmuch_message_initialize_ghost (message, *thread_id_ret);
- if (status == 0)
- /* Commit the new ghost message */
- _notmuch_message_sync (message);
- }
- } else {
- /* Create failed. Fall through. */
- }
-
- notmuch_message_destroy (message);
-
- return COERCE_STATUS (status, "Error creating ghost message");
-}
-
-/* Pre-ghost messages _resolve_message_id_to_thread_id */
-static notmuch_status_t
-_resolve_message_id_to_thread_id_old (notmuch_database_t *notmuch,
- void *ctx,
- const char *message_id,
- const char **thread_id_ret)
-{
- notmuch_status_t status;
- notmuch_message_t *message;
- string thread_id_string;
- char *metadata_key;
- Xapian::WritableDatabase *db;
-
- status = notmuch_database_find_message (notmuch, message_id, &message);
-
- if (status)
- return status;
-
- if (message) {
- *thread_id_ret = talloc_steal (ctx,
- notmuch_message_get_thread_id (message));
-
- notmuch_message_destroy (message);
-
- return NOTMUCH_STATUS_SUCCESS;
- }
-
- /* Message has not been seen yet.
- *
- * We may have seen a reference to it already, in which case, we
- * can return the thread ID stored in the metadata. Otherwise, we
- * generate a new thread ID and store it there.
- */
- db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
- metadata_key = _get_metadata_thread_id_key (ctx, message_id);
- thread_id_string = notmuch->xapian_db->get_metadata (metadata_key);
-
- if (thread_id_string.empty()) {
- *thread_id_ret = talloc_strdup (ctx,
- _notmuch_database_generate_thread_id (notmuch));
- db->set_metadata (metadata_key, *thread_id_ret);
- } else {
- *thread_id_ret = talloc_strdup (ctx, thread_id_string.c_str());
- }
-
- talloc_free (metadata_key);
-
- return NOTMUCH_STATUS_SUCCESS;
-}
-
-static notmuch_status_t
-_merge_threads (notmuch_database_t *notmuch,
- const char *winner_thread_id,
- const char *loser_thread_id)
-{
- Xapian::PostingIterator loser, loser_end;
- notmuch_message_t *message = NULL;
- notmuch_private_status_t private_status;
- notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-
- find_doc_ids (notmuch, "thread", loser_thread_id, &loser, &loser_end);
-
- for ( ; loser != loser_end; loser++) {
- message = _notmuch_message_create (notmuch, notmuch,
- *loser, &private_status);
- if (message == NULL) {
- ret = COERCE_STATUS (private_status,
- "Cannot find document for doc_id from query");
- goto DONE;
- }
-
- _notmuch_message_remove_term (message, "thread", loser_thread_id);
- _notmuch_message_add_term (message, "thread", winner_thread_id);
- _notmuch_message_sync (message);
-
- notmuch_message_destroy (message);
- message = NULL;
- }
-
- DONE:
- if (message)
- notmuch_message_destroy (message);
-
- return ret;
-}
-
-static void
-_my_talloc_free_for_g_hash (void *ptr)
-{
- talloc_free (ptr);
-}
-
-static notmuch_status_t
-_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
- notmuch_message_t *message,
- notmuch_message_file_t *message_file,
- const char **thread_id)
-{
- GHashTable *parents = NULL;
- const char *refs, *in_reply_to, *in_reply_to_message_id;
- const char *last_ref_message_id, *this_message_id;
- GList *l, *keys = NULL;
- notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
-
- parents = g_hash_table_new_full (g_str_hash, g_str_equal,
- _my_talloc_free_for_g_hash, NULL);
- this_message_id = notmuch_message_get_message_id (message);
-
- refs = _notmuch_message_file_get_header (message_file, "references");
- last_ref_message_id = parse_references (message,
- this_message_id,
- parents, refs);
-
- in_reply_to = _notmuch_message_file_get_header (message_file, "in-reply-to");
- in_reply_to_message_id = parse_references (message,
- this_message_id,
- parents, in_reply_to);
-
- /* For the parent of this message, use the last message ID of the
- * References header, if available. If not, fall back to the
- * first message ID in the In-Reply-To header. */
- if (last_ref_message_id) {
- _notmuch_message_add_term (message, "replyto",
- last_ref_message_id);
- } else if (in_reply_to_message_id) {
- _notmuch_message_add_term (message, "replyto",
- in_reply_to_message_id);
- }
-
- keys = g_hash_table_get_keys (parents);
- for (l = keys; l; l = l->next) {
- char *parent_message_id;
- const char *parent_thread_id = NULL;
-
- parent_message_id = (char *) l->data;
-
- _notmuch_message_add_term (message, "reference",
- parent_message_id);
-
- ret = _resolve_message_id_to_thread_id (notmuch,
- message,
- parent_message_id,
- &parent_thread_id);
- if (ret)
- goto DONE;
-
- if (*thread_id == NULL) {
- *thread_id = talloc_strdup (message, parent_thread_id);
- _notmuch_message_add_term (message, "thread", *thread_id);
- } else if (strcmp (*thread_id, parent_thread_id)) {
- ret = _merge_threads (notmuch, *thread_id, parent_thread_id);
- if (ret)
- goto DONE;
- }
- }
-
- DONE:
- if (keys)
- g_list_free (keys);
- if (parents)
- g_hash_table_unref (parents);
-
- return ret;
-}
-
-static notmuch_status_t
-_notmuch_database_link_message_to_children (notmuch_database_t *notmuch,
- notmuch_message_t *message,
- const char **thread_id)
-{
- const char *message_id = notmuch_message_get_message_id (message);
- Xapian::PostingIterator child, children_end;
- notmuch_message_t *child_message = NULL;
- const char *child_thread_id;
- notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
- notmuch_private_status_t private_status;
-
- find_doc_ids (notmuch, "reference", message_id, &child, &children_end);
-
- for ( ; child != children_end; child++) {
-
- child_message = _notmuch_message_create (message, notmuch,
- *child, &private_status);
- if (child_message == NULL) {
- ret = COERCE_STATUS (private_status,
- "Cannot find document for doc_id from query");
- goto DONE;
- }
-
- child_thread_id = notmuch_message_get_thread_id (child_message);
- if (*thread_id == NULL) {
- *thread_id = talloc_strdup (message, child_thread_id);
- _notmuch_message_add_term (message, "thread", *thread_id);
- } else if (strcmp (*thread_id, child_thread_id)) {
- _notmuch_message_remove_term (child_message, "reference",
- message_id);
- _notmuch_message_sync (child_message);
- ret = _merge_threads (notmuch, *thread_id, child_thread_id);
- if (ret)
- goto DONE;
- }
-
- notmuch_message_destroy (child_message);
- child_message = NULL;
- }
-
- DONE:
- if (child_message)
- notmuch_message_destroy (child_message);
-
- return ret;
-}
-
-/* Fetch and clear the stored thread_id for message, or NULL if none. */
-static char *
-_consume_metadata_thread_id (void *ctx, notmuch_database_t *notmuch,
- notmuch_message_t *message)
-{
- const char *message_id;
- string stored_id;
- char *metadata_key;
-
- message_id = notmuch_message_get_message_id (message);
- metadata_key = _get_metadata_thread_id_key (ctx, message_id);
-
- /* Check if we have already seen related messages to this one.
- * If we have then use the thread_id that we stored at that time.
- */
- stored_id = notmuch->xapian_db->get_metadata (metadata_key);
- if (stored_id.empty ()) {
- return NULL;
- } else {
- Xapian::WritableDatabase *db;
-
- db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
-
- /* Clear the metadata for this message ID. We don't need it
- * anymore. */
- db->set_metadata (metadata_key, "");
-
- return talloc_strdup (ctx, stored_id.c_str ());
- }
-}
-
-/* Given a blank or ghost 'message' and its corresponding
- * 'message_file' link it to existing threads in the database.
- *
- * First, if is_ghost, this retrieves the thread ID already stored in
- * the message (which will be the case if a message was previously
- * added that referenced this one). If the message is blank
- * (!is_ghost), it doesn't have a thread ID yet (we'll generate one
- * later in this function). If the database does not support ghost
- * messages, this checks for a thread ID stored in database metadata
- * for this message ID.
- *
- * Second, we look at 'message_file' and its link-relevant headers
- * (References and In-Reply-To) for message IDs.
- *
- * Finally, we look in the database for existing message that
- * reference 'message'.
- *
- * In all cases, we assign to the current message the first thread ID
- * found. We will also merge any existing, distinct threads where this
- * message belongs to both, (which is not uncommon when messages are
- * processed out of order).
- *
- * Finally, if no thread ID has been found through referenced messages, we
- * call _notmuch_message_generate_thread_id to generate a new thread
- * ID. This should only happen for new, top-level messages, (no
- * References or In-Reply-To header in this message, and no previously
- * added message refers to this message).
- */
-static notmuch_status_t
-_notmuch_database_link_message (notmuch_database_t *notmuch,
- notmuch_message_t *message,
- notmuch_message_file_t *message_file,
- notmuch_bool_t is_ghost)
-{
- void *local = talloc_new (NULL);
- notmuch_status_t status;
- const char *thread_id = NULL;
-
- /* Check if the message already had a thread ID */
- if (notmuch->features & NOTMUCH_FEATURE_GHOSTS) {
- if (is_ghost)
- thread_id = notmuch_message_get_thread_id (message);
- } else {
- thread_id = _consume_metadata_thread_id (local, notmuch, message);
- if (thread_id)
- _notmuch_message_add_term (message, "thread", thread_id);
- }
-
- status = _notmuch_database_link_message_to_parents (notmuch, message,
- message_file,
- &thread_id);
- if (status)
- goto DONE;
-
- if (! (notmuch->features & NOTMUCH_FEATURE_GHOSTS)) {
- /* In general, it shouldn't be necessary to link children,
- * since the earlier indexing of those children will have
- * stored a thread ID for the missing parent. However, prior
- * to ghost messages, these stored thread IDs were NOT
- * rewritten during thread merging (and there was no
- * performant way to do so), so if indexed children were
- * pulled into a different thread ID by a merge, it was
- * necessary to pull them *back* into the stored thread ID of
- * the parent. With ghost messages, we just rewrite the
- * stored thread IDs during merging, so this workaround isn't
- * necessary. */
- status = _notmuch_database_link_message_to_children (notmuch, message,
- &thread_id);
- if (status)
- goto DONE;
- }
-
- /* If not part of any existing thread, generate a new thread ID. */
- if (thread_id == NULL) {
- thread_id = _notmuch_database_generate_thread_id (notmuch);
-
- _notmuch_message_add_term (message, "thread", thread_id);
- }
-
- DONE:
- talloc_free (local);
-
- return status;
-}
-
-notmuch_status_t
-notmuch_database_add_message (notmuch_database_t *notmuch,
- const char *filename,
- notmuch_message_t **message_ret)
-{
- notmuch_message_file_t *message_file;
- notmuch_message_t *message = NULL;
- notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS, ret2;
- notmuch_private_status_t private_status;
- notmuch_bool_t is_ghost = false;
-
- const char *date, *header;
- const char *from, *to, *subject;
- char *message_id = NULL;
-
- if (message_ret)
- *message_ret = NULL;
-
- ret = _notmuch_database_ensure_writable (notmuch);
- if (ret)
- return ret;
-
- message_file = _notmuch_message_file_open (notmuch, filename);
- if (message_file == NULL)
- return NOTMUCH_STATUS_FILE_ERROR;
-
- /* Adding a message may change many documents. Do this all
- * atomically. */
- ret = notmuch_database_begin_atomic (notmuch);
- if (ret)
- goto DONE;
-
- /* Parse message up front to get better error status. */
- ret = _notmuch_message_file_parse (message_file);
- if (ret)
- goto DONE;
-
- /* Before we do any real work, (especially before doing a
- * potential SHA-1 computation on the entire file's contents),
- * let's make sure that what we're looking at looks like an
- * actual email message.
- */
- from = _notmuch_message_file_get_header (message_file, "from");
- subject = _notmuch_message_file_get_header (message_file, "subject");
- to = _notmuch_message_file_get_header (message_file, "to");
-
- if ((from == NULL || *from == '\0') &&
- (subject == NULL || *subject == '\0') &&
- (to == NULL || *to == '\0')) {
- ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
- goto DONE;
- }
-
- /* Now that we're sure it's mail, the first order of business
- * is to find a message ID (or else create one ourselves).
- */
- header = _notmuch_message_file_get_header (message_file, "message-id");
- if (header && *header != '\0') {
- message_id = _parse_message_id (message_file, header, NULL);
-
- /* So the header value isn't RFC-compliant, but it's
- * better than no message-id at all.
- */
- if (message_id == NULL)
- message_id = talloc_strdup (message_file, header);
- }
-
- if (message_id == NULL ) {
- /* No message-id at all, let's generate one by taking a
- * hash over the file's contents.
- */
- char *sha1 = _notmuch_sha1_of_file (filename);
-
- /* If that failed too, something is really wrong. Give up. */
- if (sha1 == NULL) {
- ret = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
-
- message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1);
- free (sha1);
- }
-
- try {
- /* Now that we have a message ID, we get a message object,
- * (which may or may not reference an existing document in the
- * database). */
-
- message = _notmuch_message_create_for_message_id (notmuch,
- message_id,
- &private_status);
-
- talloc_free (message_id);
-
- if (message == NULL) {
- ret = COERCE_STATUS (private_status,
- "Unexpected status value from _notmuch_message_create_for_message_id");
- goto DONE;
- }
-
- _notmuch_message_add_filename (message, filename);
-
- /* Is this a newly created message object or a ghost
- * message? We have to be slightly careful: if this is a
- * blank message, it's not safe to call
- * notmuch_message_get_flag yet. */
- if (private_status == NOTMUCH_PRIVATE_STATUS_NO_DOCUMENT_FOUND ||
- (is_ghost = notmuch_message_get_flag (
- message, NOTMUCH_MESSAGE_FLAG_GHOST))) {
- _notmuch_message_add_term (message, "type", "mail");
- if (is_ghost)
- /* Convert ghost message to a regular message */
- _notmuch_message_remove_term (message, "type", "ghost");
-
- ret = _notmuch_database_link_message (notmuch, message,
- message_file, is_ghost);
- if (ret)
- goto DONE;
-
- date = _notmuch_message_file_get_header (message_file, "date");
- _notmuch_message_set_header_values (message, date, from, subject);
-
- ret = _notmuch_message_index_file (message, message_file);
- if (ret)
- goto DONE;
- } else {
- ret = NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID;
- }
-
- _notmuch_message_sync (message);
- } catch (const Xapian::Error &error) {
- _notmuch_database_log (notmuch, "A Xapian exception occurred adding message: %s.\n",
- error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
- ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
- goto DONE;
- }
-
- DONE:
- if (message) {
- if ((ret == NOTMUCH_STATUS_SUCCESS ||
- ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) && message_ret)
- *message_ret = message;
- else
- notmuch_message_destroy (message);
- }
-
- if (message_file)
- _notmuch_message_file_close (message_file);
-
- ret2 = notmuch_database_end_atomic (notmuch);
- if ((ret == NOTMUCH_STATUS_SUCCESS ||
- ret == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) &&
- ret2 != NOTMUCH_STATUS_SUCCESS)
- ret = ret2;
-
- return ret;
-}
-
notmuch_status_t
notmuch_database_remove_message (notmuch_database_t *notmuch,
const char *filename)
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
} catch (const Xapian::Error &error) {
_notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n",
error.get_msg().c_str());
- db->exception_reported = TRUE;
+ db->exception_reported = true;
return NULL;
}
}
notmuch_directory_t *directory;
notmuch_private_status_t private_status;
const char *db_path;
- notmuch_bool_t create = (flags & NOTMUCH_FIND_CREATE);
+ bool create = (flags & NOTMUCH_FIND_CREATE);
if (! (notmuch->features & NOTMUCH_FEATURE_DIRECTORY_DOCS)) {
*status_ret = NOTMUCH_STATUS_UPGRADE_REQUIRED;
_notmuch_database_log (notmuch,
"A Xapian exception occurred creating a directory: %s.\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
notmuch_directory_destroy (directory);
directory = NULL;
*status_ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
_notmuch_database_log (notmuch,
"A Xapian exception occurred setting directory mtime: %s.\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
_notmuch_database_log (directory->notmuch,
"A Xapian exception occurred deleting directory entry: %s.\n",
error.get_msg().c_str());
- directory->notmuch->exception_reported = TRUE;
+ directory->notmuch->exception_reported = true;
status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
notmuch_directory_destroy (directory);
notmuch_filenames_valid (notmuch_filenames_t *filenames)
{
if (filenames == NULL)
- return FALSE;
+ return false;
return (filenames->iterator != NULL);
}
int next;
- g_mime_filter_set_size (gmime_filter, inlen, FALSE);
+ g_mime_filter_set_size (gmime_filter, inlen, false);
outptr = gmime_filter->outbuf;
next = filter->state;
type = g_type_register_static (GMIME_TYPE_FILTER, "NotmuchFilterDiscardNonTerm", &info, (GTypeFlags) 0);
}
- filter = (NotmuchFilterDiscardNonTerm *) g_object_newv (type, 0, NULL);
+ filter = (NotmuchFilterDiscardNonTerm *) g_object_new (type, NULL);
filter->content_type = content_type;
filter->state = 0;
if (g_mime_content_type_is_type (content_type, "text", "html")) {
}
}
+static void
+_index_content_type (notmuch_message_t *message, GMimeObject *part)
+{
+ GMimeContentType *content_type = g_mime_object_get_content_type (part);
+ if (content_type) {
+ char *mime_string = g_mime_content_type_to_string (content_type);
+ if (mime_string) {
+ _notmuch_message_gen_terms (message, "mimetype", mime_string);
+ g_free (mime_string);
+ }
+ }
+}
+
+static void
+_index_encrypted_mime_part (notmuch_message_t *message, notmuch_indexopts_t *indexopts,
+ GMimeContentType *content_type,
+ GMimeMultipartEncrypted *part);
+
/* Callback to generate terms for each mime part of a message. */
static void
_index_mime_part (notmuch_message_t *message,
+ notmuch_indexopts_t *indexopts,
GMimeObject *part)
{
GMimeStream *stream, *filter;
GMimeDataWrapper *wrapper;
GByteArray *byte_array;
GMimeContentDisposition *disposition;
+ GMimeContentType *content_type;
char *body;
const char *charset;
return;
}
- GMimeContentType *content_type = g_mime_object_get_content_type(part);
- if (content_type) {
- char *mime_string = g_mime_content_type_to_string(content_type);
- if (mime_string)
- {
- _notmuch_message_gen_terms (message, "mimetype", mime_string);
- g_free(mime_string);
- }
- }
+ _index_content_type (message, part);
+ content_type = g_mime_object_get_content_type (part);
if (GMIME_IS_MULTIPART (part)) {
GMimeMultipart *multipart = GMIME_MULTIPART (part);
for (i = 0; i < g_mime_multipart_get_count (multipart); i++) {
if (GMIME_IS_MULTIPART_SIGNED (multipart)) {
- /* Don't index the signature. */
- if (i == 1)
+ /* Don't index the signature, but index its content type. */
+ if (i == GMIME_MULTIPART_SIGNED_SIGNATURE) {
+ _index_content_type (message,
+ g_mime_multipart_get_part (multipart, i));
continue;
- if (i > 1)
+ } else if (i != GMIME_MULTIPART_SIGNED_CONTENT) {
_notmuch_database_log (_notmuch_message_database (message),
- "Warning: Unexpected extra parts of multipart/signed. Indexing anyway.\n");
+ "Warning: Unexpected extra parts of multipart/signed. Indexing anyway.\n");
+ }
}
if (GMIME_IS_MULTIPART_ENCRYPTED (multipart)) {
- /* Don't index encrypted parts. */
+ _index_content_type (message,
+ g_mime_multipart_get_part (multipart, i));
+ if (i == GMIME_MULTIPART_ENCRYPTED_CONTENT) {
+ _index_encrypted_mime_part(message, indexopts,
+ content_type,
+ GMIME_MULTIPART_ENCRYPTED (part));
+ } else {
+ if (i != GMIME_MULTIPART_ENCRYPTED_VERSION) {
+ _notmuch_database_log (_notmuch_message_database (message),
+ "Warning: Unexpected extra parts of multipart/encrypted.\n");
+ }
+ }
continue;
}
- _index_mime_part (message,
+ _index_mime_part (message, indexopts,
g_mime_multipart_get_part (multipart, i));
}
return;
mime_message = g_mime_message_part_get_message (GMIME_MESSAGE_PART (part));
- _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
+ _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message));
return;
}
byte_array = g_byte_array_new ();
stream = g_mime_stream_mem_new_with_byte_array (byte_array);
- g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), FALSE);
+ g_mime_stream_mem_set_owner (GMIME_STREAM_MEM (stream), false);
filter = g_mime_stream_filter_new (stream);
+
discard_non_term_filter = notmuch_filter_discard_non_term_new (content_type);
g_mime_stream_filter_add (GMIME_STREAM_FILTER (filter),
g_object_unref (discard_non_term_filter);
g_byte_array_append (byte_array, (guint8 *) "\0", 1);
- body = (char *) g_byte_array_free (byte_array, FALSE);
+ body = (char *) g_byte_array_free (byte_array, false);
if (body) {
_notmuch_message_gen_terms (message, NULL, body);
}
}
+/* descend (if desired) into the cleartext part of an encrypted MIME
+ * part while indexing. */
+static void
+_index_encrypted_mime_part (notmuch_message_t *message,
+ notmuch_indexopts_t *indexopts,
+ g_mime_3_unused(GMimeContentType *content_type),
+ GMimeMultipartEncrypted *encrypted_data)
+{
+ notmuch_status_t status;
+ GError *err = NULL;
+ notmuch_database_t * notmuch = NULL;
+ GMimeObject *clear = NULL;
+
+ if (!indexopts || !notmuch_indexopts_get_try_decrypt (indexopts))
+ return;
+
+ notmuch = _notmuch_message_database (message);
+
+#if (GMIME_MAJOR_VERSION < 3)
+ {
+ GMimeCryptoContext* crypto_ctx = NULL;
+ const char *protocol = NULL;
+ protocol = g_mime_content_type_get_parameter (content_type, "protocol");
+ status = _notmuch_crypto_get_gmime_ctx_for_protocol (&(indexopts->crypto),
+ protocol, &crypto_ctx);
+ if (status) {
+ _notmuch_database_log (notmuch, "Warning: setup failed for decrypting "
+ "during indexing. (%d)\n", status);
+ status = notmuch_message_add_property (message, "index.decryption", "failure");
+ if (status)
+ _notmuch_database_log_append (notmuch, "failed to add index.decryption "
+ "property (%d)\n", status);
+ return;
+ }
+ clear = g_mime_multipart_encrypted_decrypt(encrypted_data, crypto_ctx,
+ NULL, &err);
+ }
+#else
+ clear = g_mime_multipart_encrypted_decrypt(encrypted_data, GMIME_DECRYPT_NONE, NULL,
+ NULL, &err);
+#endif
+ if (err) {
+ _notmuch_database_log (notmuch, "Failed to decrypt during indexing. (%d:%d) [%s]\n",
+ err->domain, err->code, err->message);
+ g_error_free(err);
+ /* Indicate that we failed to decrypt during indexing */
+ status = notmuch_message_add_property (message, "index.decryption", "failure");
+ if (status)
+ _notmuch_database_log_append (notmuch, "failed to add index.decryption "
+ "property (%d)\n", status);
+ return;
+ }
+ _index_mime_part (message, indexopts, clear);
+ g_object_unref (clear);
+
+ status = notmuch_message_add_property (message, "index.decryption", "success");
+ if (status)
+ _notmuch_database_log (notmuch, "failed to add index.decryption "
+ "property (%d)\n", status);
+
+}
+
notmuch_status_t
_notmuch_message_index_file (notmuch_message_t *message,
+ notmuch_indexopts_t *indexopts,
notmuch_message_file_t *message_file)
{
GMimeMessage *mime_message;
subject = g_mime_message_get_subject (mime_message);
_notmuch_message_gen_terms (message, "subject", subject);
- _index_mime_part (message, g_mime_message_get_mime_part (mime_message));
+ _index_mime_part (message, indexopts, g_mime_message_get_mime_part (mime_message));
return NOTMUCH_STATUS_SUCCESS;
}
--- /dev/null
+/* indexopts.c - options for indexing messages (currently a stub)
+ *
+ * Copyright © 2017 Daniel Kahn Gillmor
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see https://www.gnu.org/licenses/ .
+ *
+ * Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ */
+
+#include "notmuch-private.h"
+
+notmuch_indexopts_t *
+notmuch_database_get_default_indexopts (notmuch_database_t *db)
+{
+ notmuch_indexopts_t *ret = talloc_zero (db, notmuch_indexopts_t);
+ if (!ret)
+ return ret;
+
+ char * try_decrypt;
+ notmuch_status_t err = notmuch_database_get_config (db, "index.try_decrypt", &try_decrypt);
+ if (err)
+ return ret;
+
+ if (try_decrypt &&
+ ((!(strcasecmp(try_decrypt, "true"))) ||
+ (!(strcasecmp(try_decrypt, "yes"))) ||
+ (!(strcasecmp(try_decrypt, "1")))))
+ notmuch_indexopts_set_try_decrypt (ret, true);
+
+ free (try_decrypt);
+ return ret;
+}
+
+notmuch_status_t
+notmuch_indexopts_set_try_decrypt (notmuch_indexopts_t *indexopts,
+ notmuch_bool_t try_decrypt)
+{
+ if (!indexopts)
+ return NOTMUCH_STATUS_NULL_POINTER;
+ indexopts->crypto.decrypt = try_decrypt;
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_bool_t
+notmuch_indexopts_get_try_decrypt (const notmuch_indexopts_t *indexopts)
+{
+ if (!indexopts)
+ return false;
+ return indexopts->crypto.decrypt;
+}
+
+void
+notmuch_indexopts_destroy (notmuch_indexopts_t *indexopts)
+{
+ talloc_free (indexopts);
+}
return _notmuch_message_file_open_ctx (notmuch, NULL, filename);
}
+const char *
+_notmuch_message_file_get_filename (notmuch_message_file_t *message_file)
+{
+ return message_file->filename;
+}
+
void
_notmuch_message_file_close (notmuch_message_file_t *message)
{
talloc_free (message);
}
-static notmuch_bool_t
+static bool
_is_mbox (FILE *file)
{
char from_buf[5];
- notmuch_bool_t ret = FALSE;
+ bool ret = false;
/* Is this mbox? */
if (fread (from_buf, sizeof (from_buf), 1, file) == 1 &&
strncmp (from_buf, "From ", 5) == 0)
- ret = TRUE;
+ ret = true;
rewind (file);
GMimeParser *parser;
notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
static int initialized = 0;
- notmuch_bool_t is_mbox;
+ bool is_mbox;
if (message->message)
return NOTMUCH_STATUS_SUCCESS;
stream = g_mime_stream_file_new (message->file);
/* We'll own and fclose the FILE* ourselves. */
- g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), FALSE);
+ g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream), false);
parser = g_mime_parser_new_with_stream (stream);
g_mime_parser_set_scan_from (parser, is_mbox);
return decoded;
}
+
+notmuch_status_t
+_notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
+ const char **from_out,
+ const char **subject_out,
+ const char **to_out,
+ const char **date_out,
+ char **message_id_out)
+{
+ notmuch_status_t ret;
+ const char *header;
+ const char *from, *to, *subject, *date;
+ char *message_id = NULL;
+
+ /* Parse message up front to get better error status. */
+ ret = _notmuch_message_file_parse (message_file);
+ if (ret)
+ goto DONE;
+
+ /* Before we do any real work, (especially before doing a
+ * potential SHA-1 computation on the entire file's contents),
+ * let's make sure that what we're looking at looks like an
+ * actual email message.
+ */
+ from = _notmuch_message_file_get_header (message_file, "from");
+ subject = _notmuch_message_file_get_header (message_file, "subject");
+ to = _notmuch_message_file_get_header (message_file, "to");
+ date = _notmuch_message_file_get_header (message_file, "date");
+
+ if ((from == NULL || *from == '\0') &&
+ (subject == NULL || *subject == '\0') &&
+ (to == NULL || *to == '\0')) {
+ ret = NOTMUCH_STATUS_FILE_NOT_EMAIL;
+ goto DONE;
+ }
+
+ /* Now that we're sure it's mail, the first order of business
+ * is to find a message ID (or else create one ourselves).
+ */
+ header = _notmuch_message_file_get_header (message_file, "message-id");
+ if (header && *header != '\0') {
+ message_id = _notmuch_message_id_parse (message_file, header, NULL);
+
+ /* So the header value isn't RFC-compliant, but it's
+ * better than no message-id at all.
+ */
+ if (message_id == NULL)
+ message_id = talloc_strdup (message_file, header);
+ }
+
+ if (message_id == NULL ) {
+ /* No message-id at all, let's generate one by taking a
+ * hash over the file's contents.
+ */
+ char *sha1 = _notmuch_sha1_of_file (_notmuch_message_file_get_filename (message_file));
+
+ /* If that failed too, something is really wrong. Give up. */
+ if (sha1 == NULL) {
+ ret = NOTMUCH_STATUS_FILE_ERROR;
+ goto DONE;
+ }
+
+ message_id = talloc_asprintf (message_file, "notmuch-sha1-%s", sha1);
+ free (sha1);
+ }
+ DONE:
+ if (ret == NOTMUCH_STATUS_SUCCESS) {
+ if (from_out)
+ *from_out = from;
+ if (subject_out)
+ *subject_out = subject;
+ if (to_out)
+ *to_out = to;
+ if (date_out)
+ *date_out = date;
+ if (message_id_out)
+ *message_id_out = message_id;
+ }
+ return ret;
+}
--- /dev/null
+#include "notmuch-private.h"
+
+/* Advance 'str' past any whitespace or RFC 822 comments. A comment is
+ * a (potentially nested) parenthesized sequence with '\' used to
+ * escape any character (including parentheses).
+ *
+ * If the sequence to be skipped continues to the end of the string,
+ * then 'str' will be left pointing at the final terminating '\0'
+ * character.
+ */
+static void
+skip_space_and_comments (const char **str)
+{
+ const char *s;
+
+ s = *str;
+ while (*s && (isspace (*s) || *s == '(')) {
+ while (*s && isspace (*s))
+ s++;
+ if (*s == '(') {
+ int nesting = 1;
+ s++;
+ while (*s && nesting) {
+ if (*s == '(') {
+ nesting++;
+ } else if (*s == ')') {
+ nesting--;
+ } else if (*s == '\\') {
+ if (*(s+1))
+ s++;
+ }
+ s++;
+ }
+ }
+ }
+
+ *str = s;
+}
+
+char *
+_notmuch_message_id_parse (void *ctx, const char *message_id, const char **next)
+{
+ const char *s, *end;
+ char *result;
+
+ if (message_id == NULL || *message_id == '\0')
+ return NULL;
+
+ s = message_id;
+
+ skip_space_and_comments (&s);
+
+ /* Skip any unstructured text as well. */
+ while (*s && *s != '<')
+ s++;
+
+ if (*s == '<') {
+ s++;
+ } else {
+ if (next)
+ *next = s;
+ return NULL;
+ }
+
+ skip_space_and_comments (&s);
+
+ end = s;
+ while (*end && *end != '>')
+ end++;
+ if (next) {
+ if (*end)
+ *next = end + 1;
+ else
+ *next = end;
+ }
+
+ if (end > s && *end == '>')
+ end--;
+ if (end <= s)
+ return NULL;
+
+ result = talloc_strndup (ctx, s, end - s + 1);
+
+ /* Finally, collapse any whitespace that is within the message-id
+ * itself. */
+ {
+ char *r;
+ int len;
+
+ for (r = result, len = strlen (r); *r; r++, len--)
+ if (*r == ' ' || *r == '\t')
+ memmove (r, r+1, len);
+ }
+
+ return result;
+}
notmuch_string_map_t *
_notmuch_message_property_map (notmuch_message_t *message);
-notmuch_bool_t
+bool
_notmuch_message_frozen (notmuch_message_t *message);
void
static notmuch_status_t
_notmuch_message_modify_property (notmuch_message_t *message, const char *key, const char *value,
- notmuch_bool_t delete_it)
+ bool delete_it)
{
notmuch_private_status_t private_status;
notmuch_status_t status;
notmuch_status_t
notmuch_message_add_property (notmuch_message_t *message, const char *key, const char *value)
{
- return _notmuch_message_modify_property (message, key, value, FALSE);
+ return _notmuch_message_modify_property (message, key, value, false);
}
notmuch_status_t
notmuch_message_remove_property (notmuch_message_t *message, const char *key, const char *value)
{
- return _notmuch_message_modify_property (message, key, value, TRUE);
+ return _notmuch_message_modify_property (message, key, value, true);
}
+static
notmuch_status_t
-notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key)
+_notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key, bool prefix)
{
notmuch_status_t status;
const char * term_prefix;
_notmuch_message_invalidate_metadata (message, "property");
if (key)
- term_prefix = talloc_asprintf (message, "%s%s=", _find_prefix ("property"), key);
+ term_prefix = talloc_asprintf (message, "%s%s%s", _find_prefix ("property"), key,
+ prefix ? "" : "=");
else
term_prefix = _find_prefix ("property");
return NOTMUCH_STATUS_SUCCESS;
}
+notmuch_status_t
+notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key)
+{
+ return _notmuch_message_remove_all_properties (message, key, false);
+}
+
+notmuch_status_t
+notmuch_message_remove_all_properties_with_prefix (notmuch_message_t *message, const char *prefix)
+{
+ return _notmuch_message_remove_all_properties (message, prefix, true);
+}
+
notmuch_message_properties_t *
notmuch_message_get_properties (notmuch_message_t *message, const char *key, notmuch_bool_t exact)
{
notmuch_string_list_t *tag_list;
notmuch_string_list_t *filename_term_list;
notmuch_string_list_t *filename_list;
+ char *maildir_flags;
char *author;
notmuch_message_file_t *message_file;
notmuch_string_list_t *property_term_list;
unsigned long lazy_flags;
/* Message document modified since last sync */
- notmuch_bool_t modified;
+ bool modified;
/* last view of database the struct is synced with */
unsigned long last_view;
struct maildir_flag_tag {
char flag;
const char *tag;
- notmuch_bool_t inverse;
+ bool inverse;
};
/* ASCII ordered table of Maildir flags and associated tags */
static struct maildir_flag_tag flag2tag[] = {
- { 'D', "draft", FALSE},
- { 'F', "flagged", FALSE},
- { 'P', "passed", FALSE},
- { 'R', "replied", FALSE},
- { 'S', "unread", TRUE }
+ { 'D', "draft", false},
+ { 'F', "flagged", false},
+ { 'P', "passed", false},
+ { 'R', "replied", false},
+ { 'S', "unread", true }
};
/* We end up having to call the destructor explicitly because we had
message->tag_list = NULL;
message->filename_term_list = NULL;
message->filename_list = NULL;
+ message->maildir_flags = NULL;
message->message_file = NULL;
message->author = NULL;
message->property_term_list = NULL;
} catch (const Xapian::Error &error) {
_notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred creating message: %s\n",
error.get_msg().c_str());
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
*status_ret = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
return NULL;
}
} catch (Xapian::Error &error) {
_notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading header: %s\n",
error.get_msg().c_str());
- message->notmuch->exception_reported = TRUE;
+ message->notmuch->exception_reported = true;
return NULL;
}
}
_notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix)
{
Xapian::TermIterator i;
- size_t prefix_len = strlen (prefix);
+ size_t prefix_len = 0;
+
+ prefix_len = strlen (prefix);
while (1) {
i = message->doc.termlist_begin ();
try {
message->doc.remove_term ((*i));
- message->modified = TRUE;
+ message->modified = true;
} catch (const Xapian::InvalidArgumentError) {
/* Ignore failure to remove non-existent term. */
}
}
}
+
+/* Remove all terms generated by indexing, i.e. not tags or
+ * properties, along with any automatic tags*/
+notmuch_private_status_t
+_notmuch_message_remove_indexed_terms (notmuch_message_t *message)
+{
+ Xapian::TermIterator i;
+
+ const std::string
+ id_prefix = _find_prefix ("id"),
+ property_prefix = _find_prefix ("property"),
+ tag_prefix = _find_prefix ("tag"),
+ type_prefix = _find_prefix ("type");
+
+ for (i = message->doc.termlist_begin ();
+ i != message->doc.termlist_end (); i++) {
+
+ const std::string term = *i;
+
+ if (term.compare (0, type_prefix.size (), type_prefix) == 0)
+ continue;
+
+ if (term.compare (0, id_prefix.size (), id_prefix) == 0)
+ continue;
+
+ if (term.compare (0, property_prefix.size (), property_prefix) == 0)
+ continue;
+
+ if (term.compare (0, tag_prefix.size (), tag_prefix) == 0 &&
+ term.compare (1, strlen("encrypted"), "encrypted") != 0 &&
+ term.compare (1, strlen("signed"), "signed") != 0 &&
+ term.compare (1, strlen("attachment"), "attachment") != 0)
+ continue;
+
+ try {
+ message->doc.remove_term ((*i));
+ message->modified = true;
+ } catch (const Xapian::InvalidArgumentError) {
+ /* Ignore failure to remove non-existent term. */
+ } catch (const Xapian::Error &error) {
+ notmuch_database_t *notmuch = message->notmuch;
+
+ if (!notmuch->exception_reported) {
+ _notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred creating message: %s\n",
+ error.get_msg().c_str());
+ notmuch->exception_reported = true;
+ }
+ return NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
+ }
+ }
+ return NOTMUCH_PRIVATE_STATUS_SUCCESS;
+}
+
/* Return true if p points at "new" or "cur". */
static bool is_maildir (const char *p)
{
talloc_free (folder);
+ message->modified = true;
return NOTMUCH_STATUS_SUCCESS;
}
_notmuch_message_clear_data (notmuch_message_t *message)
{
message->doc.set_data ("");
- message->modified = TRUE;
+ message->modified = true;
}
static void
return _notmuch_filenames_create (message, message->filename_list);
}
+int
+notmuch_message_count_files (notmuch_message_t *message)
+{
+ _notmuch_message_ensure_filename_list (message);
+
+ return _notmuch_string_list_length (message->filename_list);
+}
+
notmuch_bool_t
notmuch_message_get_flag (notmuch_message_t *message,
notmuch_message_flag_t flag)
} catch (Xapian::Error &error) {
_notmuch_database_log(_notmuch_message_database (message), "A Xapian exception occurred when reading date: %s\n",
error.get_msg().c_str());
- message->notmuch->exception_reported = TRUE;
+ message->notmuch->exception_reported = true;
return 0;
}
Xapian::sortable_serialise (time_value));
message->doc.add_value (NOTMUCH_VALUE_FROM, from);
message->doc.add_value (NOTMUCH_VALUE_SUBJECT, subject);
- message->modified = TRUE;
+ message->modified = true;
}
/* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD. The caller
{
/* _notmuch_message_sync will update the last modification
* revision; we just have to ask it to. */
- message->modified = TRUE;
+ message->modified = true;
}
/* Synchronize changes made to message->doc out into the database. */
db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
db->replace_document (message->doc_id, message->doc);
- message->modified = FALSE;
+ message->modified = false;
}
/* Delete a message document from the database, leaving a ghost
notmuch_database_t *notmuch;
notmuch_query_t *query;
unsigned int count = 0;
- notmuch_bool_t is_ghost;
+ bool is_ghost;
mid = notmuch_message_get_message_id (message);
tid = notmuch_message_get_thread_id (message);
return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG;
message->doc.add_term (term, 0);
- message->modified = TRUE;
+ message->modified = true;
talloc_free (term);
try {
message->doc.remove_term (term);
- message->modified = TRUE;
+ message->modified = true;
} catch (const Xapian::InvalidArgumentError) {
/* We'll let the philosophers try to wrestle with the
* question of whether failing to remove that which was not
_notmuch_message_has_term (notmuch_message_t *message,
const char *prefix_name,
const char *value,
- notmuch_bool_t *result)
+ bool *result)
{
char *term;
- notmuch_bool_t out = FALSE;
+ bool out = false;
notmuch_private_status_t status = NOTMUCH_PRIVATE_STATUS_SUCCESS;
if (value == NULL)
i.skip_to (term);
if (i != message->doc.termlist_end () &&
!strcmp ((*i).c_str (), term))
- out = TRUE;
+ out = true;
} catch (Xapian::Error &error) {
status = NOTMUCH_PRIVATE_STATUS_XAPIAN_EXCEPTION;
}
return NULL;
}
-notmuch_status_t
-notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
+static void
+_ensure_maildir_flags (notmuch_message_t *message, bool force)
{
const char *flags;
- notmuch_status_t status;
notmuch_filenames_t *filenames;
const char *filename, *dir;
char *combined_flags = talloc_strdup (message, "");
- unsigned i;
int seen_maildir_info = 0;
+ if (message->maildir_flags) {
+ if (force) {
+ talloc_free (message->maildir_flags);
+ message->maildir_flags = NULL;
+ }
+ }
+
for (filenames = notmuch_message_get_filenames (message);
notmuch_filenames_valid (filenames);
notmuch_filenames_move_to_next (filenames))
seen_maildir_info = 1;
}
}
+ if (seen_maildir_info)
+ message->maildir_flags = combined_flags;
+}
+
+notmuch_bool_t
+notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag)
+{
+ _ensure_maildir_flags (message, false);
+ return message->maildir_flags && (strchr (message->maildir_flags, flag) != NULL);
+}
+notmuch_status_t
+notmuch_message_maildir_flags_to_tags (notmuch_message_t *message)
+{
+ notmuch_status_t status;
+ unsigned i;
+
+ _ensure_maildir_flags (message, true);
/* If none of the filenames have any maildir info field (not even
* an empty info with no flags set) then there's no information to
* go on, so do nothing. */
- if (! seen_maildir_info)
+ if (! message->maildir_flags)
return NOTMUCH_STATUS_SUCCESS;
status = notmuch_message_freeze (message);
return status;
for (i = 0; i < ARRAY_SIZE(flag2tag); i++) {
- if ((strchr (combined_flags, flag2tag[i].flag) != NULL)
+ if ((strchr (message->maildir_flags, flag2tag[i].flag) != NULL)
^
flag2tag[i].inverse)
{
}
status = notmuch_message_thaw (message);
- talloc_free (combined_flags);
-
return status;
}
char *filename_new, *dir;
char flag_map[128];
int flags_in_map = 0;
- notmuch_bool_t flags_changed = FALSE;
+ bool flags_changed = false;
unsigned int i;
char *s;
if (flag_map[flag] == 0) {
flag_map[flag] = 1;
flags_in_map++;
- flags_changed = TRUE;
+ flags_changed = true;
}
}
if (flag_map[flag]) {
flag_map[flag] = 0;
flags_in_map--;
- flags_changed = TRUE;
+ flags_changed = true;
}
}
return message->property_map;
}
-notmuch_bool_t
+bool
_notmuch_message_frozen (notmuch_message_t *message)
{
return message->frozen;
}
+
+notmuch_status_t
+notmuch_message_reindex (notmuch_message_t *message,
+ notmuch_indexopts_t *indexopts)
+{
+ notmuch_database_t *notmuch = NULL;
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+ notmuch_private_status_t private_status;
+ notmuch_filenames_t *orig_filenames = NULL;
+ const char *orig_thread_id = NULL;
+ notmuch_message_file_t *message_file = NULL;
+
+ int found = 0;
+
+ if (message == NULL)
+ return NOTMUCH_STATUS_NULL_POINTER;
+
+ /* Save in case we need to delete message */
+ orig_thread_id = notmuch_message_get_thread_id (message);
+ if (!orig_thread_id) {
+ /* XXX TODO: make up new error return? */
+ INTERNAL_ERROR ("message without thread-id");
+ }
+
+ /* strdup it because the metadata may be invalidated */
+ orig_thread_id = talloc_strdup (message, orig_thread_id);
+
+ notmuch = _notmuch_message_database (message);
+
+ ret = _notmuch_database_ensure_writable (notmuch);
+ if (ret)
+ return ret;
+
+ orig_filenames = notmuch_message_get_filenames (message);
+
+ private_status = _notmuch_message_remove_indexed_terms (message);
+ if (private_status) {
+ ret = COERCE_STATUS(private_status, "error removing terms");
+ goto DONE;
+ }
+
+ ret = notmuch_message_remove_all_properties_with_prefix (message, "index.");
+ if (ret)
+ goto DONE; /* XXX TODO: distinguish from other error returns above? */
+
+ /* re-add the filenames with the associated indexopts */
+ for (; notmuch_filenames_valid (orig_filenames);
+ notmuch_filenames_move_to_next (orig_filenames)) {
+
+ const char *date;
+ const char *from, *to, *subject;
+ char *message_id = NULL;
+ const char *thread_id = NULL;
+
+ const char *filename = notmuch_filenames_get (orig_filenames);
+
+ message_file = _notmuch_message_file_open (notmuch, filename);
+ if (message_file == NULL)
+ continue;
+
+ ret = _notmuch_message_file_get_headers (message_file,
+ &from, &subject, &to, &date,
+ &message_id);
+ if (ret)
+ goto DONE;
+
+ /* XXX TODO: deal with changing message id? */
+
+ _notmuch_message_add_filename (message, filename);
+
+ ret = _notmuch_database_link_message_to_parents (notmuch, message,
+ message_file,
+ &thread_id);
+ if (ret)
+ goto DONE;
+
+ if (thread_id == NULL)
+ thread_id = orig_thread_id;
+
+ _notmuch_message_add_term (message, "thread", thread_id);
+ /* Take header values only from first filename */
+ if (found == 0)
+ _notmuch_message_set_header_values (message, date, from, subject);
+
+ ret = _notmuch_message_index_file (message, indexopts, message_file);
+
+ if (ret == NOTMUCH_STATUS_FILE_ERROR)
+ continue;
+ if (ret)
+ goto DONE;
+
+ found++;
+ _notmuch_message_file_close (message_file);
+ message_file = NULL;
+ }
+ if (found == 0) {
+ /* put back thread id to help cleanup */
+ _notmuch_message_add_term (message, "thread", orig_thread_id);
+ ret = _notmuch_message_delete (message);
+ } else {
+ _notmuch_message_sync (message);
+ }
+
+ DONE:
+ if (message_file)
+ _notmuch_message_file_close (message_file);
+
+ /* XXX TODO destroy orig_filenames? */
+ return ret;
+}
if (unlikely (messages == NULL))
return NULL;
- messages->is_of_list_type = TRUE;
+ messages->is_of_list_type = true;
messages->iterator = list->head;
return messages;
notmuch_messages_valid (notmuch_messages_t *messages)
{
if (messages == NULL)
- return FALSE;
+ return false;
if (! messages->is_of_list_type)
return _notmuch_mset_messages_valid (messages);
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* For getline and asprintf */
#endif
+#include <stdbool.h>
#include <stdio.h>
#include "compat.h"
#include "xutil.h"
#include "error_util.h"
#include "string-util.h"
+#include "crypto.h"
#ifdef DEBUG
# define DEBUG_DATABASE_SANITY 1
_notmuch_message_has_term (notmuch_message_t *message,
const char *prefix_name,
const char *value,
- notmuch_bool_t *result);
+ bool *result);
notmuch_private_status_t
_notmuch_message_gen_terms (notmuch_message_t *message,
_notmuch_message_file_get_header (notmuch_message_file_t *message,
const char *header);
+notmuch_status_t
+_notmuch_message_file_get_headers (notmuch_message_file_t *message_file,
+ const char **from_out,
+ const char **subject_out,
+ const char **to_out,
+ const char **date_out,
+ char **message_id_out);
+
+const char *
+_notmuch_message_file_get_filename (notmuch_message_file_t *message);
+
+/* add-message.cc */
+notmuch_status_t
+_notmuch_database_link_message_to_parents (notmuch_database_t *notmuch,
+ notmuch_message_t *message,
+ notmuch_message_file_t *message_file,
+ const char **thread_id);
/* index.cc */
notmuch_status_t
_notmuch_message_index_file (notmuch_message_t *message,
+ notmuch_indexopts_t *indexopts,
notmuch_message_file_t *message_file);
/* messages.c */
* ignorance of that here. (See notmuch_mset_messages_t in query.cc)
*/
struct _notmuch_messages {
- notmuch_bool_t is_of_list_type;
+ bool is_of_list_type;
notmuch_doc_id_set_t *excluded_doc_ids;
notmuch_message_node_t *iterator;
};
/* query.cc */
-notmuch_bool_t
+bool
_notmuch_mset_messages_valid (notmuch_messages_t *messages);
notmuch_message_t *
void
_notmuch_mset_messages_move_to_next (notmuch_messages_t *messages);
-notmuch_bool_t
+bool
_notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
unsigned int doc_id);
_notmuch_query_count_documents (notmuch_query_t *query,
const char *type,
unsigned *count_out);
+/* message-id.c */
+
+/* Parse an RFC 822 message-id, discarding whitespace, any RFC 822
+ * comments, and the '<' and '>' delimiters.
+ *
+ * If not NULL, then *next will be made to point to the first character
+ * not parsed, (possibly pointing to the final '\0' terminator.
+ *
+ * Returns a newly talloc'ed string belonging to 'ctx'.
+ *
+ * Returns NULL if there is any error parsing the message-id. */
+char *
+_notmuch_message_id_parse (void *ctx, const char *message_id, const char **next);
+
/* message.cc */
notmuch_database_t *
_notmuch_message_database (notmuch_message_t *message);
+void
+_notmuch_message_remove_unprefixed_terms (notmuch_message_t *message);
/* sha1.c */
char *
notmuch_string_list_t *
_notmuch_string_list_create (const void *ctx);
+/*
+ * return the number of strings in 'list'
+ */
+int
+_notmuch_string_list_length (notmuch_string_list_t *list);
+
/* Add 'string' to 'list'.
*
* The list will create its own talloced copy of 'string'.
notmuch_string_map_iterator_t *
_notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key,
- notmuch_bool_t exact);
+ bool exact);
-notmuch_bool_t
+bool
_notmuch_string_map_iterator_valid (notmuch_string_map_iterator_t *iter);
void
notmuch_exclude_t omit_exclude,
notmuch_sort_t sort);
+/* indexopts.c */
+
+struct _notmuch_indexopts {
+ _notmuch_crypto_t crypto;
+};
+
NOTMUCH_END_DECLS
#ifdef __cplusplus
* function, in a way not covered by a more specific argument.
*/
NOTMUCH_STATUS_ILLEGAL_ARGUMENT,
+ /**
+ * A MIME object claimed to have cryptographic protection which
+ * notmuch tried to handle, but the protocol was not specified in
+ * an intelligible way.
+ */
+ NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL,
+ /**
+ * Notmuch attempted to do crypto processing, but could not
+ * initialize the engine needed to do so.
+ */
+ NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION,
+ /**
+ * A MIME object claimed to have cryptographic protection, and
+ * notmuch attempted to process it, but the specific protocol was
+ * something that notmuch doesn't know how to handle.
+ */
+ NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL,
/**
* 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_indexopts notmuch_indexopts_t;
#endif /* __DOXYGEN__ */
/**
* The database will not yet have any data in it
* (notmuch_database_create itself is a very cheap function). Messages
* contained within 'path' can be added to the database by calling
- * notmuch_database_add_message.
+ * notmuch_database_index_file.
*
* In case of any failure, this function returns an error status and
* sets *database to NULL (after printing an error message on stderr).
notmuch_directory_t **directory);
/**
- * Add a new message to the given notmuch database or associate an
- * additional filename with an existing message.
+ * Add a message file to a database, indexing it for retrieval by
+ * future searches. If a message already exists with the same message
+ * ID as the specified file, their indexes will be merged, and this
+ * new filename will also be associated with the existing message.
*
* Here, 'filename' should be a path relative to the path of
* 'database' (see notmuch_database_get_path), or else should be an
* entire contents of the file.
*
* If another message with the same message ID already exists in the
- * database, rather than creating a new message, this adds 'filename'
- * to the list of the filenames for the existing message.
+ * database, rather than creating a new message, this adds the search
+ * terms from the identified file to the existing message's index, and
+ * adds 'filename' to the list of filenames known for the message.
+ *
+ * The 'indexopts' parameter can be NULL (meaning, use the indexing
+ * defaults from the database), or can be an explicit choice of
+ * indexing options that should govern the indexing of this specific
+ * 'filename'.
*
* If 'message' is not NULL, then, on successful return
* (NOTMUCH_STATUS_SUCCESS or NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) '*message'
*
* NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the
* database to use this function.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
*/
notmuch_status_t
+notmuch_database_index_file (notmuch_database_t *database,
+ const char *filename,
+ notmuch_indexopts_t *indexopts,
+ notmuch_message_t **message);
+
+/**
+ * Deprecated alias for notmuch_database_index_file called with
+ * NULL indexopts.
+ *
+ * @deprecated Deprecated as of libnotmuch 5.1 (notmuch 0.26). Please
+ * use notmuch_database_index_file instead.
+ *
+ */
+NOTMUCH_DEPRECATED(5,1)
+notmuch_status_t
notmuch_database_add_message (notmuch_database_t *database,
const char *filename,
notmuch_message_t **message);
int
notmuch_thread_get_total_messages (notmuch_thread_t *thread);
+/**
+ * Get the total number of files in 'thread'.
+ *
+ * This sums notmuch_message_count_files over all messages in the
+ * thread
+ * @returns Non-negative integer
+ * @since libnotmuch 5.0 (notmuch 0.25)
+ */
+
+int
+notmuch_thread_get_total_files (notmuch_thread_t *thread);
+
/**
* Get a notmuch_messages_t iterator for the top-level messages in
* 'thread' in oldest-first order.
notmuch_messages_t *
notmuch_message_get_replies (notmuch_message_t *message);
+/**
+ * Get the total number of files associated with a message.
+ * @returns Non-negative integer
+ * @since libnotmuch 5.0 (notmuch 0.25)
+ */
+int
+notmuch_message_count_files (notmuch_message_t *message);
+
/**
* Get a filename for the email corresponding to 'message'.
*
notmuch_filenames_t *
notmuch_message_get_filenames (notmuch_message_t *message);
+/**
+ * Re-index the e-mail corresponding to 'message' using the supplied index options
+ *
+ * Returns the status of the re-index operation. (see the return
+ * codes documented in notmuch_database_index_file)
+ *
+ * After reindexing, the user should discard the message object passed
+ * in here by calling notmuch_message_destroy, since it refers to the
+ * original message, not to the reindexed message.
+ */
+notmuch_status_t
+notmuch_message_reindex (notmuch_message_t *message,
+ notmuch_indexopts_t *indexopts);
+
/**
* Message flags.
*/
*
* A client can ensure that notmuch database tags remain synchronized
* with maildir flags by calling this function after each call to
- * notmuch_database_add_message. See also
+ * notmuch_database_index_file. See also
* notmuch_message_tags_to_maildir_flags for synchronizing tag changes
* back to maildir flags.
*/
notmuch_status_t
notmuch_message_maildir_flags_to_tags (notmuch_message_t *message);
+/**
+ * return TRUE if any filename of 'message' has maildir flag 'flag',
+ * FALSE otherwise.
+ *
+ */
+notmuch_bool_t
+notmuch_message_has_maildir_flag (notmuch_message_t *message, char flag);
+
/**
* Rename message filename(s) to encode tags as maildir flags.
*
* add or delete values for, as other subsystems or extensions may
* depend on these properties.
*
+ * Please see notmuch-properties(7) for more details about specific
+ * properties and conventions around their use.
+ *
*/
/**@{*/
/**
notmuch_status_t
notmuch_message_remove_all_properties (notmuch_message_t *message, const char *key);
+/**
+ * Remove all (prefix*,value) pairs from the given message
+ *
+ * @param[in,out] message message to operate on.
+ * @param[in] prefix delete properties with keys that start with prefix.
+ * If NULL, delete all properties
+ * @returns
+ * - NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in
+ * read-only mode so message cannot be modified.
+ * - NOTMUCH_STATUS_SUCCESS: No error occured.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_status_t
+notmuch_message_remove_all_properties_with_prefix (notmuch_message_t *message, const char *prefix);
+
/**
* Opaque message property iterator
*/
* says. Whereas when this function returns FALSE, calling any of
* these functions results in undefined behaviour.
*
- * See the documentation of notmuch_message_properties_get for example
+ * See the documentation of notmuch_message_get_properties for example
* code showing how to iterate over a notmuch_message_properties_t
* object.
*
*
* o Read the mtime of a directory from the filesystem
*
- * o Call add_message for all mail files in the directory
+ * o Call index_file for all mail files in the directory
*
* o Call notmuch_directory_set_mtime with the mtime read from the
* filesystem.
void
notmuch_config_list_destroy (notmuch_config_list_t *config_list);
+
+/**
+ * get the current default indexing options for a given database.
+ *
+ * This object will survive until the database itself is destroyed,
+ * but the caller may also release it earlier with
+ * notmuch_indexopts_destroy.
+ *
+ * This object represents a set of options on how a message can be
+ * added to the index. At the moment it is a featureless stub.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_indexopts_t *
+notmuch_database_get_default_indexopts (notmuch_database_t *db);
+
+/**
+ * Specify whether to decrypt encrypted parts while indexing.
+ *
+ * Be aware that the index is likely sufficient to reconstruct the
+ * cleartext of the message itself, so please ensure that the notmuch
+ * message index is adequately protected. DO NOT SET THIS FLAG TO TRUE
+ * without considering the security of your index.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_status_t
+notmuch_indexopts_set_try_decrypt (notmuch_indexopts_t *indexopts,
+ notmuch_bool_t try_decrypt);
+
+/**
+ * Return whether to decrypt encrypted parts while indexing.
+ * see notmuch_indexopts_set_try_decrypt.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+notmuch_bool_t
+notmuch_indexopts_get_try_decrypt (const notmuch_indexopts_t *indexopts);
+
+/**
+ * Destroy a notmuch_indexopts_t object.
+ *
+ * @since libnotmuch 5.1 (notmuch 0.26)
+ */
+void
+notmuch_indexopts_destroy (notmuch_indexopts_t *options);
+
+
/**
* interrogate the library for compile time features
*
notmuch_sort_t sort;
notmuch_string_list_t *exclude_terms;
notmuch_exclude_t omit_excluded;
- notmuch_bool_t parsed;
+ bool parsed;
Xapian::Query xapian_query;
std::set<std::string> terms;
};
};
/* We need this in the message functions so forward declare. */
-static notmuch_bool_t
+static bool
_notmuch_doc_id_set_init (void *ctx,
notmuch_doc_id_set_t *doc_ids,
GArray *arr);
-static notmuch_bool_t
+static bool
_debug_query (void)
{
char *env = getenv ("NOTMUCH_DEBUG_QUERY");
new (&query->xapian_query) Xapian::Query ();
new (&query->terms) std::set<std::string> ();
- query->parsed = FALSE;
+ query->parsed = false;
talloc_set_destructor (query, _notmuch_query_destructor);
t != query->xapian_query.get_terms_end (); ++t)
query->terms.insert (*t);
- query->parsed = TRUE;
+ query->parsed = true;
} catch (const Xapian::Error &error) {
if (!query->notmuch->exception_reported) {
_notmuch_database_log_append (query->notmuch,
"Query string was: %s\n",
query->query_string);
- query->notmuch->exception_reported = TRUE;
+ query->notmuch->exception_reported = true;
}
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
try {
- messages->base.is_of_list_type = FALSE;
+ messages->base.is_of_list_type = false;
messages->base.iterator = NULL;
messages->notmuch = notmuch;
new (&messages->iterator) Xapian::MSetIterator ();
mset = enquire.get_mset (0, notmuch->xapian_db->get_doccount ());
- GArray *excluded_doc_ids = g_array_new (FALSE, FALSE, sizeof (unsigned int));
+ GArray *excluded_doc_ids = g_array_new (false, false, sizeof (unsigned int));
for (iterator = mset.begin (); iterator != mset.end (); iterator++) {
unsigned int doc_id = *iterator;
switch (query->sort) {
case NOTMUCH_SORT_OLDEST_FIRST:
- enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, FALSE);
+ enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, false);
break;
case NOTMUCH_SORT_NEWEST_FIRST:
- enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, TRUE);
+ enquire.set_sort_by_value (NOTMUCH_VALUE_TIMESTAMP, true);
break;
case NOTMUCH_SORT_MESSAGE_ID:
- enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, FALSE);
+ enquire.set_sort_by_value (NOTMUCH_VALUE_MESSAGE_ID, false);
break;
case NOTMUCH_SORT_UNSORTED:
break;
"Query string was: %s\n",
query->query_string);
- notmuch->exception_reported = TRUE;
+ notmuch->exception_reported = true;
talloc_free (messages);
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
}
-notmuch_bool_t
+bool
_notmuch_mset_messages_valid (notmuch_messages_t *messages)
{
notmuch_mset_messages_t *mset_messages;
if (messages->excluded_doc_ids &&
_notmuch_doc_id_set_contains (messages->excluded_doc_ids, doc_id))
- notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, TRUE);
+ notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, true);
return message;
}
mset_messages->iterator++;
}
-static notmuch_bool_t
+static bool
_notmuch_doc_id_set_init (void *ctx,
notmuch_doc_id_set_t *doc_ids,
GArray *arr)
bitmap = talloc_zero_array (ctx, unsigned char, DOCIDSET_WORD(max) + 1);
if (bitmap == NULL)
- return FALSE;
+ return false;
doc_ids->bitmap = bitmap;
doc_ids->bound = max + 1;
bitmap[DOCIDSET_WORD(doc_id)] |= 1 << DOCIDSET_BIT(doc_id);
}
- return TRUE;
+ return true;
}
-notmuch_bool_t
+bool
_notmuch_doc_id_set_contains (notmuch_doc_id_set_t *doc_ids,
unsigned int doc_id)
{
if (doc_id >= doc_ids->bound)
- return FALSE;
+ return false;
return doc_ids->bitmap[DOCIDSET_WORD(doc_id)] & (1 << DOCIDSET_BIT(doc_id));
}
return status;
}
- threads->doc_ids = g_array_new (FALSE, FALSE, sizeof (unsigned int));
+ threads->doc_ids = g_array_new (false, false, sizeof (unsigned int));
while (notmuch_messages_valid (messages)) {
unsigned int doc_id = _notmuch_mset_messages_get_doc_id (messages);
g_array_append_val (threads->doc_ids, doc_id);
unsigned int doc_id;
if (! threads)
- return FALSE;
+ return false;
while (threads->doc_id_pos < threads->doc_ids->len) {
doc_id = g_array_index (threads->doc_ids, unsigned int,
return list;
}
+int
+_notmuch_string_list_length (notmuch_string_list_t *list)
+{
+ return list->length;
+}
+
void
_notmuch_string_list_append (notmuch_string_list_t *list,
const char *string)
} notmuch_string_pair_t;
struct _notmuch_string_map {
- notmuch_bool_t sorted;
+ bool sorted;
size_t length;
notmuch_string_pair_t *pairs;
};
struct _notmuch_string_map_iterator {
notmuch_string_pair_t *current;
- notmuch_bool_t exact;
+ bool exact;
const char *key;
};
map->length = 0;
map->pairs = NULL;
- map->sorted = TRUE;
+ map->sorted = true;
return map;
}
{
map->length++;
- map->sorted = FALSE;
+ map->sorted = false;
if (map->pairs)
map->pairs = talloc_realloc (map, map->pairs, notmuch_string_pair_t, map->length + 1);
qsort (map->pairs, map->length, sizeof (notmuch_string_pair_t), cmppair);
- map->sorted = TRUE;
+ map->sorted = true;
}
-static notmuch_bool_t
-string_cmp (const char *a, const char *b, notmuch_bool_t exact)
+static bool
+string_cmp (const char *a, const char *b, bool exact)
{
if (exact)
return (strcmp (a, b));
}
static notmuch_string_pair_t *
-bsearch_first (notmuch_string_pair_t *array, size_t len, const char *key, notmuch_bool_t exact)
+bsearch_first (notmuch_string_pair_t *array, size_t len, const char *key, bool exact)
{
size_t first = 0;
size_t last = len - 1;
/* this means that calling append invalidates iterators */
_notmuch_string_map_sort (map);
- pair = bsearch_first (map->pairs, map->length, key, TRUE);
+ pair = bsearch_first (map->pairs, map->length, key, true);
if (! pair)
return NULL;
notmuch_string_map_iterator_t *
_notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key,
- notmuch_bool_t exact)
+ bool exact)
{
notmuch_string_map_iterator_t *iter;
return iter;
}
-notmuch_bool_t
+bool
_notmuch_string_map_iterator_valid (notmuch_string_map_iterator_t *iterator)
{
if (iterator->current == NULL)
- return FALSE;
+ return false;
/* sentinel */
if (iterator->current->key == NULL)
- return FALSE;
+ return false;
return (0 == string_cmp (iterator->key, iterator->current->key, iterator->exact));
GHashTable *message_hash;
int total_messages;
+ int total_files;
int matched_messages;
time_t oldest;
time_t newest;
g_hash_table_unref (thread->message_hash);
if (thread->authors_array) {
- g_ptr_array_free (thread->authors_array, TRUE);
+ g_ptr_array_free (thread->authors_array, true);
thread->authors_array = NULL;
}
if (thread->matched_authors_array) {
- g_ptr_array_free (thread->matched_authors_array, TRUE);
+ g_ptr_array_free (thread->matched_authors_array, true);
thread->matched_authors_array = NULL;
}
first_non_matched_author = 0;
}
- g_ptr_array_free (thread->authors_array, TRUE);
+ g_ptr_array_free (thread->authors_array, true);
thread->authors_array = NULL;
- g_ptr_array_free (thread->matched_authors_array, TRUE);
+ g_ptr_array_free (thread->matched_authors_array, true);
thread->matched_authors_array = NULL;
}
InternetAddress *address;
const char *from, *author;
char *clean_author;
- notmuch_bool_t message_excluded = FALSE;
+ bool message_excluded = false;
if (omit_exclude != NOTMUCH_EXCLUDE_FALSE) {
for (tags = notmuch_message_get_tags (message);
{
/* Check for an empty string, and then ignore initial 'K'. */
if (*(term->string) && strcmp(tag, (term->string + 1)) == 0) {
- message_excluded = TRUE;
+ message_excluded = true;
break;
}
}
_notmuch_message_list_add_message (thread->message_list,
talloc_steal (thread, message));
thread->total_messages++;
+ thread->total_files += notmuch_message_count_files (message);
g_hash_table_insert (thread->message_hash,
xstrdup (notmuch_message_get_message_id (message)),
/* Mark excluded messages. */
if (message_excluded)
- notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, TRUE);
+ notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED, true);
}
static void
free, NULL);
thread->total_messages = 0;
+ thread->total_files = 0;
thread->matched_messages = 0;
thread->oldest = 0;
thread->newest = 0;
return thread->total_messages;
}
+int
+notmuch_thread_get_total_files (notmuch_thread_t *thread)
+{
+ return thread->total_files;
+}
+
int
notmuch_thread_get_matched_messages (notmuch_thread_t *thread)
{
GMimeMessage *mime_message;
/* Context provided by the caller. */
- notmuch_crypto_t *crypto;
+ _notmuch_crypto_t *crypto;
} mime_node_context_t;
static int
notmuch_status_t
mime_node_open (const void *ctx, notmuch_message_t *message,
- notmuch_crypto_t *crypto, mime_node_t **root_out)
+ _notmuch_crypto_t *crypto, mime_node_t **root_out)
{
const char *filename = notmuch_message_get_filename (message);
mime_node_context_t *mctx;
}
talloc_set_destructor (mctx, _mime_node_context_free);
+ /* Fast path */
mctx->file = fopen (filename, "r");
if (! mctx->file) {
- fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
- status = NOTMUCH_STATUS_FILE_ERROR;
- goto DONE;
- }
+ /* Slow path - for some reason the first file in the list is
+ * not available anymore. This is clearly a problem in the
+ * database, but we are not going to let this problem be a
+ * show stopper */
+ notmuch_filenames_t *filenames;
+ for (filenames = notmuch_message_get_filenames (message);
+ notmuch_filenames_valid (filenames);
+ notmuch_filenames_move_to_next (filenames))
+ {
+ filename = notmuch_filenames_get (filenames);
+ mctx->file = fopen (filename, "r");
+ if (mctx->file)
+ break;
+ }
+
+ talloc_free (filenames);
+ if (! mctx->file) {
+ /* Give up */
+ fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));
+ status = NOTMUCH_STATUS_FILE_ERROR;
+ goto DONE;
+ }
+ }
mctx->stream = g_mime_stream_file_new (mctx->file);
if (!mctx->stream) {
status = NOTMUCH_STATUS_OUT_OF_MEMORY;
goto DONE;
}
- g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), FALSE);
+ g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), false);
mctx->parser = g_mime_parser_new_with_stream (mctx->stream);
if (!mctx->parser) {
/* Verify a signed mime node (GMime 2.6) */
static void
node_verify (mime_node_t *node, GMimeObject *part,
- g_mime_3_unused(notmuch_crypto_context_t *cryptoctx))
+ g_mime_3_unused(GMimeCryptoContext *cryptoctx))
{
GError *err = NULL;
- node->verify_attempted = TRUE;
+ node->verify_attempted = true;
node->sig_list = g_mime_multipart_signed_verify
(GMIME_MULTIPART_SIGNED (part), cryptoctx, &err);
/* Decrypt and optionally verify an encrypted mime node (GMime 2.6) */
static void
node_decrypt_and_verify (mime_node_t *node, GMimeObject *part,
- g_mime_3_unused(notmuch_crypto_context_t *cryptoctx))
+ g_mime_3_unused(GMimeCryptoContext *cryptoctx))
{
GError *err = NULL;
GMimeDecryptResult *decrypt_result = NULL;
GMimeMultipartEncrypted *encrypteddata = GMIME_MULTIPART_ENCRYPTED (part);
- node->decrypt_attempted = TRUE;
+ node->decrypt_attempted = true;
node->decrypted_child = g_mime_multipart_encrypted_decrypt
+#if (GMIME_MAJOR_VERSION < 3)
(encrypteddata, cryptoctx, &decrypt_result, &err);
+#else
+ (encrypteddata, GMIME_DECRYPT_NONE, NULL, &decrypt_result, &err);
+#endif
if (! node->decrypted_child) {
fprintf (stderr, "Failed to decrypt part: %s\n",
err ? err->message : "no error explanation given");
goto DONE;
}
- node->decrypt_success = TRUE;
- node->verify_attempted = TRUE;
+ node->decrypt_success = true;
+ node->verify_attempted = true;
/* This may be NULL if the part is not signed. */
node->sig_list = g_mime_decrypt_result_get_signatures (decrypt_result);
_mime_node_create (mime_node_t *parent, GMimeObject *part)
{
mime_node_t *node = talloc_zero (parent, mime_node_t);
- notmuch_crypto_context_t *cryptoctx = NULL;
+ GMimeCryptoContext *cryptoctx = NULL;
/* Set basic node properties */
node->part = part;
|| (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->crypto->verify)) {
GMimeContentType *content_type = g_mime_object_get_content_type (part);
const char *protocol = g_mime_content_type_get_parameter (content_type, "protocol");
- cryptoctx = notmuch_crypto_get_context (node->ctx->crypto, protocol);
+ notmuch_status_t status;
+ status = _notmuch_crypto_get_gmime_ctx_for_protocol (node->ctx->crypto,
+ protocol, &cryptoctx);
+ if (status) /* this is a warning, not an error */
+ fprintf (stderr, "Warning: %s (%s).\n", notmuch_status_to_string (status),
+ protocol ? protocol : "NULL");
if (!cryptoctx)
return node;
}
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* for getline */
#endif
+#include <stdbool.h>
#include <stdio.h>
#include <sysexits.h>
#include "gmime-extra.h"
-typedef GMimeCryptoContext notmuch_crypto_context_t;
-/* This is automatically included only since gmime 2.6.10 */
-#include <gmime/gmime-pkcs7-context.h>
-
#include "notmuch.h"
/* This is separate from notmuch-private.h because we're trying to
#include <ctype.h>
#include "talloc-extra.h"
+#include "crypto.h"
#define unused(x) x __attribute__ ((unused))
const struct notmuch_show_params *params);
} notmuch_show_format_t;
-typedef struct notmuch_crypto {
- notmuch_bool_t verify;
- notmuch_bool_t decrypt;
-#if (GMIME_MAJOR_VERSION < 3)
- notmuch_crypto_context_t* gpgctx;
- notmuch_crypto_context_t* pkcs7ctx;
- const char *gpgpath;
-#endif
-} notmuch_crypto_t;
-
typedef struct notmuch_show_params {
- notmuch_bool_t entire_thread;
- notmuch_bool_t omit_excluded;
- notmuch_bool_t output_body;
+ bool entire_thread;
+ bool omit_excluded;
+ bool output_body;
int part;
- notmuch_crypto_t crypto;
- notmuch_bool_t include_html;
+ _notmuch_crypto_t crypto;
+ bool include_html;
GMimeStream *out_stream;
} notmuch_show_params_t;
void
notmuch_exit_if_unsupported_format (void);
-#if (GMIME_MAJOR_VERSION <3)
-notmuch_crypto_context_t *
-notmuch_crypto_get_context (notmuch_crypto_t *crypto, const char *protocol);
-#endif
-
-int
-notmuch_crypto_cleanup (notmuch_crypto_t *crypto);
-
int
notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]);
int
notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]);
+int
+notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[]);
+
int
notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]);
void
format_part_sprinter (const void *ctx, struct sprinter *sp, mime_node_t *node,
- notmuch_bool_t first, notmuch_bool_t output_body,
- notmuch_bool_t include_html);
+ bool output_body,
+ bool include_html);
void
format_headers_sprinter (struct sprinter *sp, GMimeMessage *message,
- notmuch_bool_t reply);
+ bool reply);
typedef enum {
NOTMUCH_SHOW_TEXT_PART_REPLY = 1 << 0,
int
notmuch_config_save (notmuch_config_t *config);
-notmuch_bool_t
+bool
notmuch_config_is_new (notmuch_config_t *config);
const char *
const char *new_ignore[],
size_t length);
-notmuch_bool_t
+bool
notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config);
void
notmuch_config_set_maildir_synchronize_flags (notmuch_config_t *config,
- notmuch_bool_t synchronize_flags);
+ bool synchronize_flags);
const char **
notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length);
int
notmuch_run_hook (const char *db_path, const char *hook);
-notmuch_bool_t
+bool
debugger_is_active (void);
/* mime-node.c */
int part_num;
/* True if decryption of this part was attempted. */
- notmuch_bool_t decrypt_attempted;
+ bool decrypt_attempted;
/* True if decryption of this part's child succeeded. In this
* case, the decrypted part is substituted for the second child of
* this part (which would usually be the encrypted data). */
- notmuch_bool_t decrypt_success;
+ bool decrypt_success;
/* True if signature verification on this part was attempted. */
- notmuch_bool_t verify_attempted;
+ bool verify_attempted;
/* The list of signatures for signed or encrypted containers. If
* there are no signatures, this will be NULL. */
*/
notmuch_status_t
mime_node_open (const void *ctx, notmuch_message_t *message,
- notmuch_crypto_t *crypto, mime_node_t **node_out);
+ _notmuch_crypto_t *crypto, mime_node_t **node_out);
/* Return a new MIME node for the requested child part of parent.
* parent will be used as the talloc context for the returned child
const char *query_str,
dump_format_t output_format,
dump_include_t include,
- notmuch_bool_t gzip_output);
+ bool gzip_output);
/* If status is non-zero (i.e. error) print appropriate
messages to stderr.
#include "command-line-arguments.h"
-extern char *notmuch_requested_db_uuid;
+extern const char *notmuch_requested_db_uuid;
extern const notmuch_opt_desc_t notmuch_shared_options [];
void notmuch_exit_if_unmatched_db_uuid (notmuch_database_t *notmuch);
void notmuch_process_shared_options (const char* subcommand_name);
int notmuch_minimal_options (const char* subcommand_name,
int argc, char **argv);
+
+
+/* the state chosen by the user invoking one of the notmuch
+ * subcommands that does indexing */
+struct _notmuch_client_indexing_cli_choices {
+ bool try_decrypt;
+ bool try_decrypt_set;
+ notmuch_indexopts_t * opts;
+};
+extern struct _notmuch_client_indexing_cli_choices indexing_cli_choices;
+extern const notmuch_opt_desc_t notmuch_shared_indexing_options [];
+notmuch_status_t
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, notmuch_config_t *config);
+
#endif
const char *path = notmuch_config_get_database_path (config);
const char *backup_path = NULL;
notmuch_status_t ret;
- notmuch_bool_t quiet = FALSE;
+ bool quiet = false;
int opt_index;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_STRING, &backup_path, "backup", 0, 0 },
- { NOTMUCH_OPT_BOOLEAN, &quiet, "quiet", 'q', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0}
+ { .opt_string = &backup_path, .name = "backup" },
+ { .opt_bool = &quiet, .name = "quiet" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
struct _notmuch_config {
char *filename;
GKeyFile *key_file;
- notmuch_bool_t is_new;
+ bool is_new;
char *database_path;
char *crypto_gpg_path;
size_t new_tags_length;
const char **new_ignore;
size_t new_ignore_length;
- notmuch_bool_t maildir_synchronize_flags;
+ bool maildir_synchronize_flags;
const char **search_exclude_tags;
size_t search_exclude_tags_length;
};
return name;
}
-static notmuch_bool_t
-get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new)
+static bool
+get_config_from_file (notmuch_config_t *config, bool create_new)
{
#define BUF_SIZE 4096
char *config_str = NULL;
int config_bufsize = BUF_SIZE;
size_t len;
GError *error = NULL;
- notmuch_bool_t ret = FALSE;
+ bool ret = false;
FILE *fp = fopen(config->filename, "r");
if (fp == NULL) {
* default configuration file in the case of FILE NOT FOUND.
*/
if (create_new) {
- config->is_new = TRUE;
- ret = TRUE;
+ config->is_new = true;
+ ret = true;
} else {
fprintf (stderr, "Configuration file %s not found.\n"
"Try running 'notmuch setup' to create a configuration.\n",
if (g_key_file_load_from_data (config->key_file, config_str, config_len,
G_KEY_FILE_KEEP_COMMENTS, &error)) {
- ret = TRUE;
+ ret = true;
goto out;
}
talloc_set_destructor (config, notmuch_config_destructor);
/* non-zero defaults */
- config->maildir_synchronize_flags = TRUE;
+ config->maildir_synchronize_flags = true;
if (filename) {
config->filename = talloc_strdup (config, filename);
config->key_file = g_key_file_new ();
if (config_mode & NOTMUCH_CONFIG_OPEN) {
- notmuch_bool_t create_new = (config_mode & NOTMUCH_CONFIG_CREATE) != 0;
+ bool create_new = (config_mode & NOTMUCH_CONFIG_CREATE) != 0;
if (! get_config_from_file (config, create_new)) {
talloc_free (config);
g_key_file_get_boolean (config->key_file,
"maildir", "synchronize_flags", &error);
if (error) {
- notmuch_config_set_maildir_synchronize_flags (config, TRUE);
+ notmuch_config_set_maildir_synchronize_flags (config, true);
g_error_free (error);
}
return 0;
}
-notmuch_bool_t
+bool
notmuch_config_is_new (notmuch_config_t *config)
{
return config->is_new;
}
#define BUILT_WITH_PREFIX "built_with."
-#define QUERY_PREFIX "query."
+
+static bool
+_stored_in_db (const char *item)
+{
+ const char * db_configs[] = {
+ "index.try_decrypt",
+ };
+ if (STRNCMP_LITERAL (item, "query.") == 0)
+ return true;
+ for (size_t i = 0; i < ARRAY_SIZE (db_configs); i++)
+ if (strcmp (item, db_configs[i]) == 0)
+ return true;
+ return false;
+}
static int
_print_db_config(notmuch_config_t *config, const char *name)
} else if (STRNCMP_LITERAL (item, BUILT_WITH_PREFIX) == 0) {
printf ("%s\n",
notmuch_built_with (item + strlen (BUILT_WITH_PREFIX)) ? "true" : "false");
- } else if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
+ } else if (_stored_in_db (item)) {
return _print_db_config (config, item);
} else {
char **value;
return 1;
}
- if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) {
+ if (_stored_in_db (item)) {
return _set_db_config (config, item, argc, argv);
}
}
-notmuch_bool_t
+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,
- notmuch_bool_t synchronize_flags)
+ bool synchronize_flags)
{
g_key_file_set_boolean (config->key_file,
"maildir", "synchronize_flags", synchronize_flags);
OUTPUT_FILES,
};
-/* The following is to allow future options to be added more easily */
-enum {
- EXCLUDE_TRUE,
- EXCLUDE_FALSE,
-};
-
/* Return the number of files matching the query, or -1 for an error */
static int
count_files (notmuch_query_t *query)
char *query_str;
int opt_index;
int output = OUTPUT_MESSAGES;
- int exclude = EXCLUDE_TRUE;
+ bool exclude = true;
const char **search_exclude_tags = NULL;
size_t search_exclude_tags_length = 0;
- notmuch_bool_t batch = FALSE;
- notmuch_bool_t print_lastmod = FALSE;
+ bool batch = false;
+ bool print_lastmod = false;
FILE *input = stdin;
- char *input_file_name = NULL;
+ const char *input_file_name = NULL;
int ret;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &output, "output", 'o',
+ { .opt_keyword = &output, .name = "output", .keywords =
(notmuch_keyword_t []){ { "threads", OUTPUT_THREADS },
{ "messages", OUTPUT_MESSAGES },
{ "files", OUTPUT_FILES },
{ 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD, &exclude, "exclude", 'x',
- (notmuch_keyword_t []){ { "true", EXCLUDE_TRUE },
- { "false", EXCLUDE_FALSE },
- { 0, 0 } } },
- { NOTMUCH_OPT_BOOLEAN, &print_lastmod, "lastmod", 'l', 0 },
- { NOTMUCH_OPT_BOOLEAN, &batch, "batch", 0, 0 },
- { NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_bool = &exclude, .name = "exclude" },
+ { .opt_bool = &print_lastmod, .name = "lastmod" },
+ { .opt_bool = &batch, .name = "batch" },
+ { .opt_string = &input_file_name, .name = "input" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
notmuch_process_shared_options (argv[0]);
if (input_file_name) {
- batch = TRUE;
+ batch = true;
input = fopen (input_file_name, "r");
if (input == NULL) {
fprintf (stderr, "Error opening %s for reading: %s\n",
if (batch && opt_index != argc) {
fprintf (stderr, "--batch and query string are not compatible\n");
+ if (input)
+ fclose (input);
return EXIT_FAILURE;
}
return EXIT_FAILURE;
}
- if (exclude == EXCLUDE_TRUE) {
+ if (exclude) {
search_exclude_tags = notmuch_config_get_search_exclude_tags
(config, &search_exclude_tags_length);
}
{
const char *message_id;
notmuch_message_properties_t *list;
- notmuch_bool_t first = TRUE;
+ bool first = true;
message_id = notmuch_message_get_message_id (message);
return 0;
}
- for (list = notmuch_message_get_properties (message, "", FALSE);
+ for (list = notmuch_message_get_properties (message, "", false);
notmuch_message_properties_valid (list); notmuch_message_properties_move_to_next (list)) {
const char *key, *val;
return 1;
}
gzprintf (output, "#= %s", *buffer_p);
- first = FALSE;
+ first = false;
}
key = notmuch_message_properties_key (list);
const char *query_str,
dump_format_t output_format,
dump_include_t include,
- notmuch_bool_t gzip_output)
+ bool gzip_output)
{
gzFile output = NULL;
const char *mode = gzip_output ? "w9" : "wT";
notmuch_exit_if_unmatched_db_uuid (notmuch);
- char *output_file_name = NULL;
+ const char *output_file_name = NULL;
int opt_index;
int output_format = DUMP_FORMAT_BATCH_TAG;
int include = 0;
- notmuch_bool_t gzip_output = 0;
+ bool gzip_output = 0;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &output_format, "format", 'f',
+ { .opt_keyword = &output_format, .name = "format", .keywords =
(notmuch_keyword_t []){ { "sup", DUMP_FORMAT_SUP },
{ "batch-tag", DUMP_FORMAT_BATCH_TAG },
{ 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',
+ { .opt_flags = &include, .name = "include", .keywords =
(notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
{ "properties", DUMP_INCLUDE_PROPERTIES },
{ "tags", DUMP_INCLUDE_TAGS} } },
- { NOTMUCH_OPT_STRING, &output_file_name, "output", 'o', 0 },
- { NOTMUCH_OPT_BOOLEAN, &gzip_output, "gzip", 'z', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_string = &output_file_name, .name = "output" },
+ { .opt_bool = &gzip_output, .name = "gzip" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include "string-util.h"
static volatile sig_atomic_t interrupted;
}
/* Call fsync() on a directory path. */
-static notmuch_bool_t
+static bool
sync_dir (const char *dir)
{
int fd, r;
fd = open (dir, O_RDONLY);
if (fd == -1) {
fprintf (stderr, "Error: open %s: %s\n", dir, strerror (errno));
- return FALSE;
+ return false;
}
r = fsync (fd);
/*
* Check the specified folder name does not contain a directory
* component ".." to prevent writes outside of the Maildir
- * hierarchy. Return TRUE on valid folder name, FALSE otherwise.
+ * hierarchy. Return true on valid folder name, false otherwise.
*/
-static notmuch_bool_t
+static bool
is_valid_folder_name (const char *folder)
{
const char *p = folder;
for (;;) {
if ((p[0] == '.') && (p[1] == '.') && (p[2] == '\0' || p[2] == '/'))
- return FALSE;
+ return false;
p = strchr (p, '/');
if (!p)
- return TRUE;
+ return true;
p++;
}
}
/*
* Make the given directory and its parents as necessary, using the
- * given mode. Return TRUE on success, FALSE otherwise. Partial
+ * given mode. Return true on success, false otherwise. Partial
* results are not cleaned up on errors.
*/
-static notmuch_bool_t
+static bool
mkdir_recursive (const void *ctx, const char *path, int mode)
{
struct stat st;
if (! S_ISDIR (st.st_mode)) {
fprintf (stderr, "Error: '%s' is not a directory: %s\n",
path, strerror (EEXIST));
- return FALSE;
+ return false;
}
- return TRUE;
+ return true;
} else if (errno != ENOENT) {
fprintf (stderr, "Error: stat '%s': %s\n", path, strerror (errno));
- return FALSE;
+ return false;
}
/* mkdir parents, if any */
parent = talloc_strndup (ctx, path, slash - path);
if (! parent) {
fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
- return FALSE;
+ return false;
}
if (! mkdir_recursive (ctx, parent, mode))
- return FALSE;
+ return false;
}
if (mkdir (path, mode)) {
fprintf (stderr, "Error: mkdir '%s': %s\n", path, strerror (errno));
- return FALSE;
+ return false;
}
- return parent ? sync_dir (parent) : TRUE;
+ return parent ? sync_dir (parent) : true;
}
/*
* Create the given maildir folder, i.e. maildir and its
- * subdirectories cur/new/tmp. Return TRUE on success, FALSE
+ * subdirectories cur/new/tmp. Return true on success, false
* otherwise. Partial results are not cleaned up on errors.
*/
-static notmuch_bool_t
+static bool
maildir_create_folder (const void *ctx, const char *maildir)
{
const char *subdirs[] = { "cur", "new", "tmp" };
subdir = talloc_asprintf (ctx, "%s/%s", maildir, subdirs[i]);
if (! subdir) {
fprintf (stderr, "Error: %s\n", strerror (ENOMEM));
- return FALSE;
+ return false;
}
if (! mkdir_recursive (ctx, subdir, mode))
- return FALSE;
+ return false;
}
- return TRUE;
+ return true;
}
/*
}
/*
- * Copy fdin to fdout, return TRUE on success, and FALSE on errors and
+ * Copy fdin to fdout, return true on success, and false on errors and
* empty input.
*/
-static notmuch_bool_t
+static bool
copy_fd (int fdout, int fdin)
{
- notmuch_bool_t empty = TRUE;
+ bool empty = true;
while (! interrupted) {
ssize_t remain;
continue;
fprintf (stderr, "Error: reading from standard input: %s\n",
strerror (errno));
- return FALSE;
+ return false;
}
p = buf;
if (written <= 0) {
fprintf (stderr, "Error: writing to temporary file: %s",
strerror (errno));
- return FALSE;
+ return false;
}
p += written;
remain -= written;
- empty = FALSE;
+ empty = false;
} while (remain > 0);
}
/*
* Add the specified message file to the notmuch database, applying
- * tags in tag_ops. If synchronize_flags is TRUE, the tags are
+ * tags in tag_ops. If synchronize_flags is true, the tags are
* synchronized to maildir flags (which may result in message file
* rename).
*
* Return NOTMUCH_STATUS_SUCCESS on success, errors otherwise. If keep
- * is TRUE, errors in tag changes and flag syncing are ignored and
+ * is true, errors in tag changes and flag syncing are ignored and
* success status is returned; otherwise such errors cause the message
* to be removed from the database. Failure to add the message to the
* database results in error status regardless of keep.
*/
static notmuch_status_t
add_file (notmuch_database_t *notmuch, const char *path, tag_op_list_t *tag_ops,
- notmuch_bool_t synchronize_flags, notmuch_bool_t keep)
+ bool synchronize_flags, bool keep,
+ notmuch_indexopts_t *indexopts)
{
notmuch_message_t *message;
notmuch_status_t status;
- status = notmuch_database_add_message (notmuch, path, &message);
+ status = notmuch_database_index_file (notmuch, path, indexopts, &message);
if (status == NOTMUCH_STATUS_SUCCESS) {
status = tag_op_list_apply (message, tag_ops, 0);
if (status) {
size_t new_tags_length;
tag_op_list_t *tag_ops;
char *query_string = NULL;
- const char *folder = NULL;
- notmuch_bool_t create_folder = FALSE;
- notmuch_bool_t keep = FALSE;
- notmuch_bool_t no_hooks = FALSE;
- notmuch_bool_t synchronize_flags;
- const char *maildir;
+ const char *folder = "";
+ bool create_folder = false;
+ bool keep = false;
+ bool no_hooks = false;
+ bool synchronize_flags;
+ char *maildir;
char *newpath;
int opt_index;
unsigned int i;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_STRING, &folder, "folder", 0, 0 },
- { NOTMUCH_OPT_BOOLEAN, &create_folder, "create-folder", 0, 0 },
- { NOTMUCH_OPT_BOOLEAN, &keep, "keep", 0, 0 },
- { NOTMUCH_OPT_BOOLEAN, &no_hooks, "no-hooks", 'n', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { NOTMUCH_OPT_END, 0, 0, 0, 0 }
+ { .opt_string = &folder, .name = "folder" },
+ { .opt_bool = &create_folder, .name = "create-folder" },
+ { .opt_bool = &keep, .name = "keep" },
+ { .opt_bool = &no_hooks, .name = "no-hooks" },
+ { .opt_inherit = notmuch_shared_indexing_options },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
for (i = 0; i < new_tags_length; i++) {
const char *error_msg;
- error_msg = illegal_tag (new_tags[i], FALSE);
+ error_msg = illegal_tag (new_tags[i], false);
if (error_msg) {
fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
new_tags[i], error_msg);
return EXIT_FAILURE;
}
- if (tag_op_list_append (tag_ops, new_tags[i], FALSE))
+ if (tag_op_list_append (tag_ops, new_tags[i], false))
return EXIT_FAILURE;
}
return EXIT_FAILURE;
}
- if (folder == NULL) {
- maildir = db_path;
- } else {
- if (! is_valid_folder_name (folder)) {
- fprintf (stderr, "Error: invalid folder name: '%s'\n", folder);
- return EXIT_FAILURE;
- }
- maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
- if (! maildir) {
- fprintf (stderr, "Out of memory\n");
- return EXIT_FAILURE;
- }
- if (create_folder && ! maildir_create_folder (config, maildir))
- return EXIT_FAILURE;
+ if (! is_valid_folder_name (folder)) {
+ fprintf (stderr, "Error: invalid folder name: '%s'\n", folder);
+ return EXIT_FAILURE;
}
+ maildir = talloc_asprintf (config, "%s/%s", db_path, folder);
+ if (! maildir) {
+ fprintf (stderr, "Out of memory\n");
+ return EXIT_FAILURE;
+ }
+
+ strip_trailing (maildir, '/');
+ if (create_folder && ! maildir_create_folder (config, maildir))
+ return EXIT_FAILURE;
+
/* Set up our handler for SIGINT. We do not set SA_RESTART so that copying
* from standard input may be interrupted. */
memset (&action, 0, sizeof (struct sigaction));
notmuch_exit_if_unmatched_db_uuid (notmuch);
+ status = notmuch_process_shared_indexing_options (notmuch, config);
+ if (status != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+ notmuch_status_to_string (status));
+ return EXIT_FAILURE;
+ }
/* Index the message. */
- status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep);
+ status = add_file (notmuch, newpath, tag_ops, synchronize_flags, keep, indexing_cli_choices.opts);
/* Commit changes. */
close_status = notmuch_database_destroy (notmuch);
typedef struct {
int output_is_a_tty;
enum verbosity verbosity;
- notmuch_bool_t debug;
+ bool debug;
const char **new_tags;
size_t new_tags_length;
const char **new_ignore;
_filename_list_t *removed_directories;
_filename_list_t *directory_mtimes;
- notmuch_bool_t synchronize_flags;
+ bool synchronize_flags;
} add_files_state_t;
static volatile sig_atomic_t do_print_progress = 0;
return 0;
}
+static bool
+_special_directory (const char *entry)
+{
+ return strcmp (entry, ".") == 0 || strcmp (entry, "..") == 0;
+}
+
/* Test if the file/directory is to be ignored.
*/
-static notmuch_bool_t
+static bool
_entry_in_ignore_list (const char *entry, add_files_state_t *state)
{
size_t i;
for (i = 0; i < state->new_ignore_length; i++)
if (strcmp (entry, state->new_ignore[i]) == 0)
- return TRUE;
+ return true;
- return FALSE;
+ return false;
}
/* Add a single file to the database. */
if (status)
goto DONE;
- status = notmuch_database_add_message (notmuch, filename, &message);
+ status = notmuch_database_index_file (notmuch, filename, indexing_cli_choices.opts, &message);
switch (status) {
/* Success. */
case NOTMUCH_STATUS_SUCCESS:
state->added_messages++;
notmuch_message_freeze (message);
- for (tag = state->new_tags; *tag != NULL; tag++)
- notmuch_message_add_tag (message, *tag);
if (state->synchronize_flags)
notmuch_message_maildir_flags_to_tags (message);
+
+ for (tag = state->new_tags; *tag != NULL; tag++) {
+ if (strcmp ("unread", *tag) !=0 ||
+ !notmuch_message_has_maildir_flag (message, 'S')) {
+ notmuch_message_add_tag (message, *tag);
+ }
+ }
+
notmuch_message_thaw (message);
break;
/* Non-fatal issues (go on to next file). */
case NOTMUCH_STATUS_READ_ONLY_DATABASE:
case NOTMUCH_STATUS_XAPIAN_EXCEPTION:
case NOTMUCH_STATUS_OUT_OF_MEMORY:
- fprintf (stderr, "Error: %s. Halting processing.\n",
- notmuch_status_to_string (status));
+ (void) print_status_database("add_file", notmuch, status);
goto DONE;
default:
INTERNAL_ERROR ("add_message returned unexpected value: %d", status);
notmuch_filenames_t *db_subdirs = NULL;
time_t stat_time;
struct stat st;
- notmuch_bool_t is_maildir;
+ bool is_maildir;
if (stat (path, &st)) {
fprintf (stderr, "Error reading directory %s: %s\n",
/* Pass 1: Recurse into all sub-directories. */
is_maildir = _entries_resemble_maildir (path, fs_entries, num_fs_entries);
- for (i = 0; i < num_fs_entries; i++) {
- if (interrupted)
- break;
-
+ for (i = 0; i < num_fs_entries && ! interrupted; i++) {
entry = fs_entries[i];
+ /* Ignore special directories to avoid infinite recursion. */
+ if (_special_directory (entry->d_name))
+ continue;
+
/* Ignore any files/directories the user has configured to
* ignore. We do this before dirent_type both for performance
* and because we don't care if dirent_type fails on entries
continue;
}
- /* Ignore special directories to avoid infinite recursion.
- * Also ignore the .notmuch directory and any "tmp" directory
+ /* Ignore the .notmuch directory and any "tmp" directory
* that appears within a maildir.
*/
- if (strcmp (entry->d_name, ".") == 0 ||
- strcmp (entry->d_name, "..") == 0 ||
- (is_maildir && strcmp (entry->d_name, "tmp") == 0) ||
+ if ((is_maildir && strcmp (entry->d_name, "tmp") == 0) ||
strcmp (entry->d_name, ".notmuch") == 0)
continue;
}
/* Pass 2: Scan for new files, removed files, and removed directories. */
- for (i = 0; i < num_fs_entries; i++)
- {
- if (interrupted)
- break;
-
+ for (i = 0; i < num_fs_entries && ! interrupted; i++) {
entry = fs_entries[i];
+ /* Ignore special directories early. */
+ if (_special_directory (entry->d_name))
+ continue;
+
/* Ignore files & directories user has configured to be ignored */
if (_entry_in_ignore_list (entry->d_name, state)) {
if (state->debug)
/* Ignore special directories to avoid infinite recursion.
* Also ignore the .notmuch directory.
*/
- if (strcmp (entry->d_name, ".") == 0 ||
- strcmp (entry->d_name, "..") == 0 ||
+ if (_special_directory (entry->d_name) ||
strcmp (entry->d_name, ".notmuch") == 0)
continue;
status = notmuch_database_remove_message (notmuch, path);
if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID) {
add_files_state->renamed_messages++;
- if (add_files_state->synchronize_flags == TRUE)
+ if (add_files_state->synchronize_flags == true)
notmuch_message_maildir_flags_to_tags (message);
status = NOTMUCH_STATUS_SUCCESS;
} else if (status == NOTMUCH_STATUS_SUCCESS) {
notmuch_database_t *notmuch;
add_files_state_t add_files_state = {
.verbosity = VERBOSITY_NORMAL,
- .debug = FALSE,
+ .debug = false,
.output_is_a_tty = isatty (fileno (stdout)),
};
struct timeval tv_start;
_filename_node_t *f;
int opt_index;
unsigned int i;
- notmuch_bool_t timer_is_active = FALSE;
- notmuch_bool_t no_hooks = FALSE;
- notmuch_bool_t quiet = FALSE, verbose = FALSE;
+ bool timer_is_active = false;
+ bool no_hooks = false;
+ bool quiet = false, verbose = false;
notmuch_status_t status;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_BOOLEAN, &quiet, "quiet", 'q', 0 },
- { NOTMUCH_OPT_BOOLEAN, &verbose, "verbose", 'v', 0 },
- { NOTMUCH_OPT_BOOLEAN, &add_files_state.debug, "debug", 'd', 0 },
- { NOTMUCH_OPT_BOOLEAN, &no_hooks, "no-hooks", 'n', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_bool = &quiet, .name = "quiet" },
+ { .opt_bool = &verbose, .name = "verbose" },
+ { .opt_bool = &add_files_state.debug, .name = "debug" },
+ { .opt_bool = &no_hooks, .name = "no-hooks" },
+ { .opt_inherit = notmuch_shared_indexing_options },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
for (i = 0; i < add_files_state.new_tags_length; i++) {
const char *error_msg;
- error_msg = illegal_tag (add_files_state.new_tags[i], FALSE);
+ error_msg = illegal_tag (add_files_state.new_tags[i], false);
if (error_msg) {
fprintf (stderr, "Error: tag '%s' in new.tags: %s\n",
add_files_state.new_tags[i], error_msg);
}
if (notmuch_database_dump (notmuch, backup_name, "",
- DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, TRUE)) {
+ DUMP_FORMAT_BATCH_TAG, DUMP_INCLUDE_DEFAULT, true)) {
fprintf (stderr, "Backup failed. Aborting upgrade.");
return EXIT_FAILURE;
}
if (notmuch == NULL)
return EXIT_FAILURE;
+ status = notmuch_process_shared_indexing_options (notmuch, config);
+ if (status != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+ notmuch_status_to_string (status));
+ return EXIT_FAILURE;
+ }
+
/* Set up our handler for SIGINT. We do this after having
* potentially done a database upgrade we this interrupt handler
* won't support. */
if (add_files_state.verbosity == VERBOSITY_NORMAL &&
add_files_state.output_is_a_tty && ! debugger_is_active ()) {
setup_progress_printing_timer ();
- timer_is_active = TRUE;
+ timer_is_active = true;
}
ret = add_files (notmuch, db_path, &add_files_state);
--- /dev/null
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2016 Daniel Kahn Gillmor
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/ .
+ *
+ * Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+ */
+
+#include "notmuch-client.h"
+#include "string-util.h"
+
+static volatile sig_atomic_t interrupted;
+
+static void
+handle_sigint (unused (int sig))
+{
+ static char msg[] = "Stopping... \n";
+
+ /* This write is "opportunistic", so it's okay to ignore the
+ * result. It is not required for correctness, and if it does
+ * fail or produce a short write, we want to get out of the signal
+ * handler as quickly as possible, not retry it. */
+ IGNORE_RESULT (write (2, msg, sizeof (msg) - 1));
+ interrupted = 1;
+}
+
+/* reindex all messages matching 'query_string' using the passed-in indexopts
+ */
+static int
+reindex_query (notmuch_database_t *notmuch, const char *query_string,
+ notmuch_indexopts_t *indexopts)
+{
+ notmuch_query_t *query;
+ notmuch_messages_t *messages;
+ notmuch_message_t *message;
+ notmuch_status_t status;
+
+ notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+
+ query = notmuch_query_create (notmuch, query_string);
+ if (query == NULL) {
+ fprintf (stderr, "Out of memory.\n");
+ return 1;
+ }
+
+ /* reindexing is not interested in any special sort order */
+ notmuch_query_set_sort (query, NOTMUCH_SORT_UNSORTED);
+
+ status = notmuch_query_search_messages (query, &messages);
+ if (print_status_query ("notmuch reindex", query, status))
+ return status;
+
+ ret = notmuch_database_begin_atomic (notmuch);
+ for (;
+ notmuch_messages_valid (messages) && ! interrupted;
+ notmuch_messages_move_to_next (messages)) {
+ message = notmuch_messages_get (messages);
+
+ ret = notmuch_message_reindex(message, indexopts);
+ if (ret != NOTMUCH_STATUS_SUCCESS)
+ break;
+ }
+
+ if (!ret)
+ ret = notmuch_database_end_atomic (notmuch);
+
+ notmuch_query_destroy (query);
+
+ return ret || interrupted;
+}
+
+int
+notmuch_reindex_command (notmuch_config_t *config, int argc, char *argv[])
+{
+ char *query_string = NULL;
+ notmuch_database_t *notmuch;
+ struct sigaction action;
+ int opt_index;
+ int ret;
+ notmuch_status_t status;
+
+ /* Set up our handler for SIGINT */
+ memset (&action, 0, sizeof (struct sigaction));
+ action.sa_handler = handle_sigint;
+ sigemptyset (&action.sa_mask);
+ action.sa_flags = SA_RESTART;
+ sigaction (SIGINT, &action, NULL);
+
+ notmuch_opt_desc_t options[] = {
+ { .opt_inherit = notmuch_shared_indexing_options },
+ { .opt_inherit = notmuch_shared_options },
+ { }
+ };
+
+ opt_index = parse_arguments (argc, argv, options, 1);
+ if (opt_index < 0)
+ return EXIT_FAILURE;
+
+ 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, config);
+ if (status != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "Error: Failed to process index options. (%s)\n",
+ notmuch_status_to_string (status));
+ return EXIT_FAILURE;
+ }
+
+ query_string = query_string_from_args (config, argc-opt_index, argv+opt_index);
+ if (query_string == NULL) {
+ fprintf (stderr, "Out of memory\n");
+ return EXIT_FAILURE;
+ }
+
+ if (*query_string == '\0') {
+ fprintf (stderr, "Error: notmuch reindex requires at least one search term.\n");
+ return EXIT_FAILURE;
+ }
+
+ ret = reindex_query (notmuch, query_string, indexing_cli_choices.opts);
+
+ notmuch_database_destroy (notmuch);
+
+ return ret || interrupted ? EXIT_FAILURE : EXIT_SUCCESS;
+}
} address_match_t;
/* Match given string against given address according to mode. */
-static notmuch_bool_t
+static bool
match_address (const char *str, const char *address, address_match_t mode)
{
switch (mode) {
return strcasecmp (address, str) == 0;
}
- return FALSE;
+ return false;
}
/* Match given string against user's configured "primary" and "other"
/* Is the given address configured as one of the user's "primary" or
* "other" addresses. */
-static notmuch_bool_t
+static bool
address_is_users (const char *address, notmuch_config_t *config)
{
return address_match (address, config, STRING_IS_USER_ADDRESS) != NULL;
/* Does the address in the Reply-To header of 'message' already appear
* in either the 'To' or 'Cc' header of the message?
*/
-static notmuch_bool_t
+static bool
reply_to_header_is_redundant (GMimeMessage *message,
InternetAddressList *reply_to_list)
{
InternetAddress *address;
InternetAddressMailbox *mailbox;
InternetAddressList *recipients;
- notmuch_bool_t ret = FALSE;
+ bool ret = false;
int i;
if (reply_to_list == NULL ||
mailbox = INTERNET_ADDRESS_MAILBOX (address);
addr = internet_address_mailbox_get_addr (mailbox);
if (strcmp (addr, reply_to) == 0) {
- ret = TRUE;
+ ret = true;
break;
}
}
add_recipients_from_message (GMimeMessage *reply,
notmuch_config_t *config,
GMimeMessage *message,
- notmuch_bool_t reply_all)
+ bool reply_all)
{
/* There is a memory leak here with gmime-2.6 because get_sender
notmuch_config_t *config,
notmuch_message_t *message,
GMimeMessage *mime_message,
- notmuch_bool_t reply_all,
- notmuch_bool_t limited)
+ bool reply_all,
+ bool limited)
{
const char *subject, *from_addr = NULL;
const char *in_reply_to, *orig_references, *references;
notmuch_query_t *query,
notmuch_show_params_t *params,
int format,
- notmuch_bool_t reply_all)
+ bool reply_all)
{
GMimeMessage *reply;
mime_node_t *node;
/* The headers of the reply message we've created */
sp->map_key (sp, "reply-headers");
- format_headers_sprinter (sp, reply, TRUE);
+ format_headers_sprinter (sp, reply, true);
/* Start the original */
sp->map_key (sp, "original");
- format_part_sprinter (config, sp, node, TRUE, TRUE, FALSE);
+ format_part_sprinter (config, sp, node, true, false);
/* End */
sp->end (sp);
.part = -1,
};
int format = FORMAT_DEFAULT;
- int reply_all = TRUE;
+ int reply_all = true;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
+ { .opt_keyword = &format, .name = "format", .keywords =
(notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
{ "json", FORMAT_JSON },
{ "sexp", FORMAT_SEXP },
{ "headers-only", FORMAT_HEADERS_ONLY },
{ 0, 0 } } },
- { NOTMUCH_OPT_INT, ¬much_format_version, "format-version", 0, 0 },
- { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
- (notmuch_keyword_t []){ { "all", TRUE },
- { "sender", FALSE },
+ { .opt_int = ¬much_format_version, .name = "format-version" },
+ { .opt_keyword = &reply_all, .name = "reply-to", .keywords =
+ (notmuch_keyword_t []){ { "all", true },
+ { "sender", false },
{ 0, 0 } } },
- { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_bool = ¶ms.crypto.decrypt, .name = "decrypt" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
if (do_reply (config, query, ¶ms, format, reply_all) != 0)
return EXIT_FAILURE;
- notmuch_crypto_cleanup (¶ms.crypto);
+ _notmuch_crypto_cleanup (¶ms.crypto);
notmuch_query_destroy (query);
notmuch_database_destroy (notmuch);
tok_len++;
}
- if (tag_op_list_append (tag_ops, tok, FALSE))
+ if (tag_op_list_append (tag_ops, tok, false))
return -1;
}
notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[])
{
notmuch_database_t *notmuch;
- notmuch_bool_t accumulate = FALSE;
+ bool accumulate = false;
tag_op_flag_t flags = 0;
tag_op_list_t *tag_ops;
- char *input_file_name = NULL;
+ const char *input_file_name = NULL;
const char *name_for_error = NULL;
gzFile input = NULL;
char *line = NULL;
flags |= TAG_FLAG_MAILDIR_SYNC;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &input_format, "format", 'f',
+ { .opt_keyword = &input_format, .name = "format", .keywords =
(notmuch_keyword_t []){ { "auto", DUMP_FORMAT_AUTO },
{ "batch-tag", DUMP_FORMAT_BATCH_TAG },
{ "sup", DUMP_FORMAT_SUP },
{ 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD_FLAGS, &include, "include", 'I',
+ { .opt_flags = &include, .name = "include", .keywords =
(notmuch_keyword_t []){ { "config", DUMP_INCLUDE_CONFIG },
{ "properties", DUMP_INCLUDE_PROPERTIES },
{ "tags", DUMP_INCLUDE_TAGS} } },
- { NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
- { NOTMUCH_OPT_BOOLEAN, &accumulate, "accumulate", 'a', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_string = &input_file_name, .name = "input" },
+ { .opt_bool = &accumulate, .name = "accumulate" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
typedef struct {
notmuch_database_t *notmuch;
- format_sel_t format_sel;
+ int format_sel;
sprinter_t *format;
- notmuch_exclude_t exclude;
+ int exclude;
notmuch_query_t *query;
- notmuch_sort_t sort;
- output_t output;
+ int sort;
+ int output;
int offset;
int limit;
int dupe;
GHashTable *addresses;
- dedup_t dedup;
+ int dedup;
} search_context_t;
typedef struct {
const char *subject = notmuch_thread_get_subject (thread);
const char *thread_id = notmuch_thread_get_thread_id (thread);
int matched = notmuch_thread_get_matched_messages (thread);
+ int files = notmuch_thread_get_total_files (thread);
int total = notmuch_thread_get_total_messages (thread);
const char *relative_date = NULL;
- notmuch_bool_t first_tag = TRUE;
+ bool first_tag = true;
format->begin_map (format);
if (format->is_text_printer) {
/* Special case for the text formatter */
- printf ("thread:%s %12s [%d/%d] %s; %s (",
+ printf ("thread:%s %12s ",
thread_id,
- relative_date,
+ relative_date);
+ if (total == files)
+ printf ("[%d/%d] %s; %s (",
matched,
total,
sanitize_string (ctx_quote, authors),
sanitize_string (ctx_quote, subject));
+ else
+ printf ("[%d/%d(%d)] %s; %s (",
+ matched,
+ total,
+ files,
+ sanitize_string (ctx_quote, authors),
+ sanitize_string (ctx_quote, subject));
+
} else { /* Structured Output */
format->map_key (format, "thread");
format->string (format, thread_id);
if (format->is_text_printer) {
/* Special case for the text formatter */
if (first_tag)
- first_tag = FALSE;
+ first_tag = false;
else
fputc (' ', stdout);
fputs (tag, stdout);
return ret;
}
-/* Returns TRUE iff name and addr is duplicate. If not, stores the
+/* Returns true iff name and addr is duplicate. If not, stores the
* name/addr pair in order to detect subsequent duplicates. */
-static notmuch_bool_t
+static bool
is_duplicate (const search_context_t *ctx, const char *name, const char *addr)
{
char *key;
if (l) {
mailbox = l->data;
mailbox->count++;
- return TRUE;
+ return true;
}
mailbox = new_mailbox (ctx->format, name, addr);
if (! mailbox)
- return FALSE;
+ return false;
/*
* XXX: It would be more efficient to prepend to the list, but
if (list != g_list_append (list, mailbox))
INTERNAL_ERROR ("appending to list changed list head\n");
- return FALSE;
+ return false;
}
key = talloc_strdup (ctx->format, addr);
if (! key)
- return FALSE;
+ return false;
mailbox = new_mailbox (ctx->format, name, addr);
if (! mailbox)
- return FALSE;
+ return false;
list = g_list_append (NULL, mailbox);
if (! list)
- return FALSE;
+ return false;
g_hash_table_insert (ctx->addresses, key, list);
- return FALSE;
+ return false;
}
static void
/* name_addr has the name part quoted if necessary. Compare
* 'John Doe <john@doe.com>' vs. '"Doe, John" <john@doe.com>' */
- name_addr = internet_address_to_string (ia, FALSE);
+ name_addr = internet_address_to_string (ia, false);
if (format->is_text_printer) {
if (ctx->output & OUTPUT_COUNT) {
};
static const notmuch_opt_desc_t common_options[] = {
- { NOTMUCH_OPT_KEYWORD, &search_context.sort, "sort", 's',
+ { .opt_keyword = &search_context.sort, .name = "sort", .keywords =
(notmuch_keyword_t []){ { "oldest-first", NOTMUCH_SORT_OLDEST_FIRST },
{ "newest-first", NOTMUCH_SORT_NEWEST_FIRST },
{ 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD, &search_context.format_sel, "format", 'f',
+ { .opt_keyword = &search_context.format_sel, .name = "format", .keywords =
(notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
{ "sexp", NOTMUCH_FORMAT_SEXP },
{ "text", NOTMUCH_FORMAT_TEXT },
{ "text0", NOTMUCH_FORMAT_TEXT0 },
{ 0, 0 } } },
- { NOTMUCH_OPT_INT, ¬much_format_version, "format-version", 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_int = ¬much_format_version, .name = "format-version" },
+ { }
};
int
int opt_index, ret;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &ctx->output, "output", 'o',
+ { .opt_keyword = &ctx->output, .name = "output", .keywords =
(notmuch_keyword_t []){ { "summary", OUTPUT_SUMMARY },
{ "threads", OUTPUT_THREADS },
{ "messages", OUTPUT_MESSAGES },
{ "files", OUTPUT_FILES },
{ "tags", OUTPUT_TAGS },
{ 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD, &ctx->exclude, "exclude", 'x',
+ { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
(notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
{ "false", NOTMUCH_EXCLUDE_FALSE },
{ "flag", NOTMUCH_EXCLUDE_FLAG },
{ "all", NOTMUCH_EXCLUDE_ALL },
{ 0, 0 } } },
- { NOTMUCH_OPT_INT, &ctx->offset, "offset", 'O', 0 },
- { NOTMUCH_OPT_INT, &ctx->limit, "limit", 'L', 0 },
- { NOTMUCH_OPT_INT, &ctx->dupe, "duplicate", 'D', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) &common_options, NULL, 0, 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_int = &ctx->offset, .name = "offset" },
+ { .opt_int = &ctx->limit, .name = "limit" },
+ { .opt_int = &ctx->dupe, .name = "duplicate" },
+ { .opt_inherit = common_options },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
ctx->output = OUTPUT_SUMMARY;
int opt_index, ret;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD_FLAGS, &ctx->output, "output", 'o',
+ { .opt_flags = &ctx->output, .name = "output", .keywords =
(notmuch_keyword_t []){ { "sender", OUTPUT_SENDER },
{ "recipients", OUTPUT_RECIPIENTS },
{ "count", OUTPUT_COUNT },
{ 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD, &ctx->exclude, "exclude", 'x',
+ { .opt_keyword = &ctx->exclude, .name = "exclude", .keywords =
(notmuch_keyword_t []){ { "true", NOTMUCH_EXCLUDE_TRUE },
{ "false", NOTMUCH_EXCLUDE_FALSE },
{ 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD, &ctx->dedup, "deduplicate", 'D',
+ { .opt_keyword = &ctx->dedup, .name = "deduplicate", .keywords =
(notmuch_keyword_t []){ { "no", DEDUP_NONE },
{ "mailbox", DEDUP_MAILBOX },
{ "address", DEDUP_ADDRESS },
{ 0, 0 } } },
- { NOTMUCH_OPT_INHERIT, (void *) &common_options, NULL, 0, 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_inherit = common_options },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
(const char **)
other_emails->pdata,
other_emails->len);
- g_ptr_array_free (other_emails, TRUE);
+ g_ptr_array_free (other_emails, true);
prompt ("Top-level directory of your email archive [%s]: ",
notmuch_config_get_database_path (config));
notmuch_config_set_new_tags (config, (const char **) tags->pdata,
tags->len);
- g_ptr_array_free (tags, TRUE);
+ g_ptr_array_free (tags, true);
}
(const char **) tags->pdata,
tags->len);
- g_ptr_array_free (tags, TRUE);
+ g_ptr_array_free (tags, true);
}
if (notmuch_config_save (config))
void
format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
- notmuch_bool_t reply)
+ bool reply)
{
/* Any changes to the JSON or S-Expression format should be
* reflected in the file devel/schemata. */
return;
stream_filter = g_mime_stream_filter_new (stream_out);
- crlf_filter = g_mime_filter_crlf_new (FALSE, FALSE);
+ crlf_filter = g_mime_filter_crlf_new (false, false);
g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
crlf_filter);
g_object_unref (crlf_filter);
if (flags & NOTMUCH_SHOW_TEXT_PART_REPLY) {
GMimeFilter *reply_filter;
- reply_filter = g_mime_filter_reply_new (TRUE);
+ reply_filter = g_mime_filter_reply_new (true);
if (reply_filter) {
g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
reply_filter);
for (unsigned int i = 0; i < array_map_len; i++) {
if (errors & key_map[i].bit) {
sp->map_key (sp, key_map[i].string);
- sp->boolean (sp, TRUE);
+ sp->boolean (sp, true);
}
}
GMimeObject *meta = node->envelope_part ?
GMIME_OBJECT (node->envelope_part) : node->part;
GMimeContentType *content_type = g_mime_object_get_content_type (meta);
- const notmuch_bool_t leaf = GMIME_IS_PART (node->part);
+ const bool leaf = GMIME_IS_PART (node->part);
GMimeStream *stream = params->out_stream;
const char *part_type;
int i;
void
format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
- notmuch_bool_t first, notmuch_bool_t output_body,
- notmuch_bool_t include_html)
+ bool output_body,
+ bool include_html)
{
/* Any changes to the JSON or S-Expression format should be
* reflected in the file devel/schemata. */
format_message_sprinter (sp, node->envelope_file);
sp->map_key (sp, "headers");
- format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
+ format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false);
if (output_body) {
sp->map_key (sp, "body");
sp->begin_list (sp);
- format_part_sprinter (ctx, sp, mime_node_child (node, 0), first, TRUE, include_html);
+ format_part_sprinter (ctx, sp, mime_node_child (node, 0), true, include_html);
sp->end (sp);
}
sp->end (sp);
sp->begin_map (sp);
sp->map_key (sp, "headers");
- format_headers_sprinter (sp, GMIME_MESSAGE (node->part), FALSE);
+ format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false);
sp->map_key (sp, "body");
sp->begin_list (sp);
}
for (i = 0; i < node->nchildren; i++)
- format_part_sprinter (ctx, sp, mime_node_child (node, i), i == 0, TRUE, include_html);
+ format_part_sprinter (ctx, sp, mime_node_child (node, i), true, include_html);
/* Close content structures */
for (i = 0; i < nclose; i++)
mime_node_t *node, unused (int indent),
const notmuch_show_params_t *params)
{
- format_part_sprinter (ctx, sp, node, TRUE, params->output_body, params->include_html);
+ format_part_sprinter (ctx, sp, node, params->output_body, params->include_html);
return NOTMUCH_STATUS_SUCCESS;
}
notmuch_show_params_t *params)
{
notmuch_message_t *message;
- notmuch_bool_t match;
- notmuch_bool_t excluded;
+ bool match;
+ bool excluded;
int next_indent;
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
sprinter_t *sprinter;
notmuch_show_params_t params = {
.part = -1,
- .omit_excluded = TRUE,
- .output_body = TRUE,
+ .omit_excluded = true,
+ .output_body = true,
};
int format = NOTMUCH_FORMAT_NOT_SPECIFIED;
- int exclude = TRUE;
-
- /* This value corresponds to neither true nor false being passed
- * on the command line */
- int entire_thread = -1;
- notmuch_bool_t single_message;
+ bool exclude = true;
+ bool entire_thread_set = false;
+ bool single_message;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
+ { .opt_keyword = &format, .name = "format", .keywords =
(notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
{ "text", NOTMUCH_FORMAT_TEXT },
{ "sexp", NOTMUCH_FORMAT_SEXP },
{ "mbox", NOTMUCH_FORMAT_MBOX },
{ "raw", NOTMUCH_FORMAT_RAW },
{ 0, 0 } } },
- { NOTMUCH_OPT_INT, ¬much_format_version, "format-version", 0, 0 },
- { NOTMUCH_OPT_BOOLEAN, &exclude, "exclude", 'x', 0 },
- { NOTMUCH_OPT_BOOLEAN, &entire_thread, "entire-thread", 't', 0 },
- { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 },
- { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.decrypt, "decrypt", 'd', 0 },
- { NOTMUCH_OPT_BOOLEAN, ¶ms.crypto.verify, "verify", 'v', 0 },
- { NOTMUCH_OPT_BOOLEAN, ¶ms.output_body, "body", 'b', 0 },
- { NOTMUCH_OPT_BOOLEAN, ¶ms.include_html, "include-html", 0, 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_int = ¬much_format_version, .name = "format-version" },
+ { .opt_bool = &exclude, .name = "exclude" },
+ { .opt_bool = ¶ms.entire_thread, .name = "entire-thread",
+ .present = &entire_thread_set },
+ { .opt_int = ¶ms.part, .name = "part" },
+ { .opt_bool = ¶ms.crypto.decrypt, .name = "decrypt" },
+ { .opt_bool = ¶ms.crypto.verify, .name = "verify" },
+ { .opt_bool = ¶ms.output_body, .name = "body" },
+ { .opt_bool = ¶ms.include_html, .name = "include-html" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
/* decryption implies verification */
if (params.crypto.decrypt)
- params.crypto.verify = TRUE;
+ params.crypto.verify = true;
/* specifying a part implies single message display */
single_message = params.part >= 0;
}
} else if (format == NOTMUCH_FORMAT_RAW) {
/* raw format only supports single message display */
- single_message = TRUE;
+ single_message = true;
}
notmuch_exit_if_unsupported_format ();
- /* Default is entire-thread = FALSE except for format=json and
+ /* Default is entire-thread = false except for format=json and
* format=sexp. */
- if (entire_thread != FALSE && entire_thread != TRUE) {
- if (format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP)
- params.entire_thread = TRUE;
- else
- params.entire_thread = FALSE;
- } else {
- params.entire_thread = entire_thread;
- }
+ if (! entire_thread_set &&
+ (format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP))
+ params.entire_thread = true;
if (!params.output_body) {
if (params.part > 0) {
fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
- params.output_body = TRUE;
+ params.output_body = true;
} else {
if (format != NOTMUCH_FORMAT_JSON && format != NOTMUCH_FORMAT_SEXP)
fprintf (stderr,
}
}
- if (exclude == FALSE) {
- notmuch_query_set_omit_excluded (query, FALSE);
- params.omit_excluded = FALSE;
+ if (exclude == false) {
+ notmuch_query_set_omit_excluded (query, false);
+ params.omit_excluded = false;
}
ret = do_show (config, query, formatter, sprinter, ¶ms);
g_mime_stream_flush (params.out_stream);
g_object_unref (params.out_stream);
- notmuch_crypto_cleanup (¶ms.crypto);
+ _notmuch_crypto_cleanup (¶ms.crypto);
notmuch_query_destroy (query);
notmuch_database_destroy (notmuch);
notmuch_database_t *notmuch;
struct sigaction action;
tag_op_flag_t tag_flags = TAG_FLAG_NONE;
- notmuch_bool_t batch = FALSE;
- notmuch_bool_t remove_all = FALSE;
+ bool batch = false;
+ bool remove_all = false;
FILE *input = stdin;
- char *input_file_name = NULL;
+ const char *input_file_name = NULL;
int opt_index;
int ret;
sigaction (SIGINT, &action, NULL);
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_BOOLEAN, &batch, "batch", 0, 0 },
- { NOTMUCH_OPT_STRING, &input_file_name, "input", 'i', 0 },
- { NOTMUCH_OPT_BOOLEAN, &remove_all, "remove-all", 0, 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_bool = &batch, .name = "batch" },
+ { .opt_string = &input_file_name, .name = "input" },
+ { .opt_bool = &remove_all, .name = "remove-all" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
notmuch_process_shared_options (argv[0]);
if (input_file_name) {
- batch = TRUE;
+ batch = true;
input = fopen (input_file_name, "r");
if (input == NULL) {
fprintf (stderr, "Error opening %s for reading: %s\n",
if (batch) {
if (opt_index != argc) {
fprintf (stderr, "Can't specify both cmdline and stdin!\n");
+ if (input)
+ fclose (input);
return EXIT_FAILURE;
}
} else {
static int
_help_for (const char *topic);
-static notmuch_bool_t print_version = FALSE, print_help = FALSE;
-char *notmuch_requested_db_uuid = NULL;
+static bool print_version = false, print_help = false;
+const char *notmuch_requested_db_uuid = NULL;
const notmuch_opt_desc_t notmuch_shared_options [] = {
- { NOTMUCH_OPT_BOOLEAN, &print_version, "version", 'v', 0 },
- { NOTMUCH_OPT_BOOLEAN, &print_help, "help", 'h', 0 },
- { NOTMUCH_OPT_STRING, ¬much_requested_db_uuid, "uuid", 'u', 0 },
- {0, 0, 0, 0, 0}
+ { .opt_bool = &print_version, .name = "version" },
+ { .opt_bool = &print_help, .name = "help" },
+ { .opt_string = ¬much_requested_db_uuid, .name = "uuid" },
+ { }
};
/* any subcommand wanting to support these options should call
int opt_index;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
opt_index = parse_arguments (argc, argv, options, 1);
return opt_index;
}
+
+struct _notmuch_client_indexing_cli_choices indexing_cli_choices = { };
+const notmuch_opt_desc_t notmuch_shared_indexing_options [] = {
+ { .opt_bool = &indexing_cli_choices.try_decrypt,
+ .present = &indexing_cli_choices.try_decrypt_set,
+ .name = "try-decrypt" },
+ { }
+};
+
+
+notmuch_status_t
+notmuch_process_shared_indexing_options (notmuch_database_t *notmuch, g_mime_3_unused(notmuch_config_t *config))
+{
+ if (indexing_cli_choices.opts == NULL)
+ indexing_cli_choices.opts = notmuch_database_get_default_indexopts (notmuch);
+ if (indexing_cli_choices.try_decrypt_set) {
+ notmuch_status_t status;
+ if (indexing_cli_choices.opts == NULL)
+ return NOTMUCH_STATUS_OUT_OF_MEMORY;
+ status = notmuch_indexopts_set_try_decrypt (indexing_cli_choices.opts, indexing_cli_choices.try_decrypt);
+ if (status != NOTMUCH_STATUS_SUCCESS) {
+ fprintf (stderr, "Error: Failed to set try_decrypt to %s. (%s)\n",
+ indexing_cli_choices.try_decrypt ? "True" : "False", notmuch_status_to_string (status));
+ notmuch_indexopts_destroy (indexing_cli_choices.opts);
+ indexing_cli_choices.opts = NULL;
+ return status;
+ }
+ }
+#if (GMIME_MAJOR_VERSION < 3)
+ if (indexing_cli_choices.opts && notmuch_indexopts_get_try_decrypt (indexing_cli_choices.opts)) {
+ const char* gpg_path = notmuch_config_get_crypto_gpg_path (config);
+ if (gpg_path && strcmp(gpg_path, "gpg"))
+ fprintf (stderr, "Warning: deprecated crypto.gpg_path is set to '%s'\n"
+ "\tbut ignoring (use $PATH instead)\n", gpg_path);
+ }
+#endif
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+
static command_t commands[] = {
{ NULL, notmuch_command, NOTMUCH_CONFIG_OPEN | NOTMUCH_CONFIG_CREATE,
"Notmuch main command." },
"Restore the tags from the given dump file (see 'dump')." },
{ "compact", notmuch_compact_command, NOTMUCH_CONFIG_OPEN,
"Compact the notmuch database." },
+ { "reindex", notmuch_reindex_command, NOTMUCH_CONFIG_OPEN,
+ "Re-index all messages matching the search terms." },
{ "config", notmuch_config_command, NOTMUCH_CONFIG_OPEN,
"Get or set settings in the notmuch configuration file." },
{ "help", notmuch_help_command, NOTMUCH_CONFIG_CREATE, /* create but don't save config */
* is).
*
* Does not return if the external command is found and
- * executed. Return TRUE if external command is not found. Return
- * FALSE on errors.
+ * executed. Return true if external command is not found. Return
+ * false on errors.
*/
-static notmuch_bool_t try_external_command(char *argv[])
+static bool try_external_command(char *argv[])
{
char *old_argv0 = argv[0];
- notmuch_bool_t ret = TRUE;
+ bool ret = true;
argv[0] = talloc_asprintf (NULL, "notmuch-%s", old_argv0);
if (errno != ENOENT) {
fprintf (stderr, "Error: Running external command '%s' failed: %s\n",
argv[0], strerror(errno));
- ret = FALSE;
+ ret = false;
}
talloc_free (argv[0]);
char *talloc_report;
const char *command_name = NULL;
command_t *command;
- char *config_file_name = NULL;
+ const char *config_file_name = NULL;
notmuch_config_t *config = NULL;
int opt_index;
int ret;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_STRING, &config_file_name, "config", 'c', 0 },
- { NOTMUCH_OPT_INHERIT, (void *) ¬much_shared_options, NULL, 0, 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_string = &config_file_name, .name = "config" },
+ { .opt_inherit = notmuch_shared_options },
+ { }
};
talloc_enable_null_tracking ();
-tmp.*/
-log.*/
-corpus/
-notmuch.cache.*/
+/tmp.*/
+/log.*/
+/corpus/
+/notmuch.cache.*/
test_description='notmuch new'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
# ensure initial 'notmuch new' is run by memory_start
uncache_database
test_description='dump and restore'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
memory_start
test_description='show'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
memory_start
test_description='search'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
memory_start
test_description='search'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
memory_start
--- /dev/null
+#!/bin/bash
+
+test_description='reindex'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+memory_run 'reindex *' "notmuch reindex '*'"
+
+memory_done
--- /dev/null
+#!/bin/bash
+
+test_description='search'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+memory_start
+
+mkdir -p "$MAIL_DIR"/{cur,new,tmp}
+
+for count in {1..20}; do
+ generate_message "[file]=\"insert-$count\"" "[dir]='tmp/'"
+ memory_run "insert $count" "notmuch insert < $gen_msg_filename"
+done
+
+memory_done
test_description='notmuch new'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
uncache_database
test_description='dump and restore'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
time_start
test_description='tagging'
-. ./perf-test-lib.sh || exit 1
+. $(dirname "$0")/perf-test-lib.sh || exit 1
time_start
--- /dev/null
+#!/bin/bash
+
+test_description='tagging'
+
+. $(dirname "$0")/perf-test-lib.sh || exit 1
+
+time_start
+
+time_run 'reindex *' "notmuch reindex '*'"
+time_run 'reindex *' "notmuch reindex '*'"
+time_run 'reindex *' "notmuch reindex '*'"
+
+time_done
-*.tar.gz
-*.tar.xz
+/*.tar.gz
+/*.tar.xz
-. ./version.sh || exit 1
+. $(dirname "$0")/version.sh || exit 1
corpus_size=large
echo "error: unknown performance test option '$1'" >&2; exit 1 ;;
esac
done
-. ../test/test-lib-common.sh || exit 1
+
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/../test/export-dirs.sh || exit 1
+
+. "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
set -e
-if ! test -x ../notmuch
-then
+# It appears that people try to run tests without building...
+if [[ ! -x "$NOTMUCH_BUILDDIR/notmuch" ]]; then
echo >&2 'You do not seem to have built notmuch yet.'
exit 1
fi
/* A flag to signify that a separator should be inserted in the
* output as soon as possible.
*/
- notmuch_bool_t insert_separator;
+ bool insert_separator;
};
struct json_state {
struct json_state *parent;
/* True if nothing has been printed in this aggregate yet.
* Suppresses the comma before a value. */
- notmuch_bool_t first;
+ bool first;
/* The character that closes the current aggregate. */
char close;
};
fputc (',', spj->stream);
if (spj->insert_separator) {
fputc ('\n', spj->stream);
- spj->insert_separator = FALSE;
+ spj->insert_separator = false;
} else {
fputc (' ', spj->stream);
}
} else {
- spj->state->first = FALSE;
+ spj->state->first = false;
}
}
return spj;
fputc (open, spj->stream);
state->parent = spj->state;
- state->first = TRUE;
+ state->first = true;
state->close = close;
spj->state = state;
}
}
static void
-json_boolean (struct sprinter *sp, notmuch_bool_t val)
+json_boolean (struct sprinter *sp, bool val)
{
struct sprinter_json *spj = json_begin_value (sp);
json_string (sp, key);
fputs (": ", spj->stream);
- spj->state->first = TRUE;
+ spj->state->first = true;
}
static void
{
struct sprinter_json *spj = (struct sprinter_json *) sp;
- spj->insert_separator = TRUE;
+ spj->insert_separator = true;
}
struct sprinter *
.map_key = json_map_key,
.separator = json_separator,
.set_prefix = json_set_prefix,
- .is_text_printer = FALSE,
+ .is_text_printer = false,
}
};
struct sprinter_json *res;
/* A flag to signify that a separator should be inserted in the
* output as soon as possible. */
- notmuch_bool_t insert_separator;
+ bool insert_separator;
};
struct sexp_state {
/* True if nothing has been printed in this aggregate yet.
* Suppresses the space before a value. */
- notmuch_bool_t first;
+ bool first;
};
/* Helper function to set up the stream to print a value. If this
if (! sps->state->first) {
if (sps->insert_separator) {
fputc ('\n', sps->stream);
- sps->insert_separator = FALSE;
+ sps->insert_separator = false;
} else {
fputc (' ', sps->stream);
}
} else {
- sps->state->first = FALSE;
+ sps->state->first = false;
}
}
return sps;
fputc ('(', sps->stream);
state->parent = sps->state;
- state->first = TRUE;
+ state->first = true;
sps->state = state;
}
}
static void
-sexp_boolean (struct sprinter *sp, notmuch_bool_t val)
+sexp_boolean (struct sprinter *sp, bool val)
{
struct sprinter_sexp *sps = sexp_begin_value (sp);
{
struct sprinter_sexp *sps = (struct sprinter_sexp *) sp;
- sps->insert_separator = TRUE;
+ sps->insert_separator = true;
}
struct sprinter *
.map_key = sexp_map_key,
.separator = sexp_separator,
.set_prefix = sexp_set_prefix,
- .is_text_printer = FALSE,
+ .is_text_printer = false,
}
};
struct sprinter_sexp *res;
/* A flag to indicate if this is the first tag. Used in list of tags
* for summary.
*/
- notmuch_bool_t first_tag;
+ bool first_tag;
};
static void
}
static void
-text_boolean (struct sprinter *sp, notmuch_bool_t val)
+text_boolean (struct sprinter *sp, bool val)
{
struct sprinter_text *sptxt = (struct sprinter_text *) sp;
.map_key = text_map_key,
.separator = text_separator,
.set_prefix = text_set_prefix,
- .is_text_printer = TRUE,
+ .is_text_printer = true,
},
};
struct sprinter_text *res;
#ifndef NOTMUCH_SPRINTER_H
#define NOTMUCH_SPRINTER_H
-/* Necessary for notmuch_bool_t */
+/* Necessary for bool */
#include "notmuch-client.h"
/* Structure printer interface. This is used to create output
void (*string) (struct sprinter *, const char *);
void (*string_len) (struct sprinter *, const char *, size_t);
void (*integer) (struct sprinter *, int);
- void (*boolean) (struct sprinter *, notmuch_bool_t);
+ void (*boolean) (struct sprinter *, bool);
void (*null) (struct sprinter *);
/* Print the key of a map's key/value pair. The char * must be UTF-8
/* True if this is the special-cased plain text printer.
*/
- notmuch_bool_t is_text_printer;
+ bool is_text_printer;
} sprinter_t;
struct _tag_operation_t {
const char *tag;
- notmuch_bool_t remove;
+ bool remove;
};
struct _tag_op_list_t {
}
const char *
-illegal_tag (const char *tag, notmuch_bool_t remove)
+illegal_tag (const char *tag, bool remove)
{
if (*tag == '\0' && ! remove)
return "empty tag forbidden";
/* Parse tags. */
while ((tok = strtok_len (tok + tok_len, " ", &tok_len)) != NULL) {
- notmuch_bool_t remove;
+ bool remove;
char *tag;
/* Optional explicit end of tags marker. */
if (argv[i][0] != '+' && argv[i][0] != '-')
break;
- notmuch_bool_t is_remove = argv[i][0] == '-';
+ bool is_remove = argv[i][0] == '-';
const char *msg;
msg = illegal_tag (argv[i] + 1, is_remove);
size_t i;
notmuch_tags_t *tags;
- notmuch_bool_t changes = FALSE;
+ bool changes = false;
/* First, do we delete an existing tag? */
for (tags = notmuch_message_get_tags (message);
notmuch_tags_destroy (tags);
if (changes)
- return TRUE;
+ return true;
/* Now check for adding new tags */
for (i = 0; i < list->count; i++) {
- notmuch_bool_t exists = FALSE;
+ bool exists = false;
if (list->ops[i].remove)
continue;
notmuch_tags_move_to_next (tags)) {
const char *cur_tag = notmuch_tags_get (tags);
if (strcmp (cur_tag, list->ops[i].tag) == 0) {
- exists = TRUE;
+ exists = true;
break;
}
}
* but this is OK from a correctness point of view
*/
if (! exists)
- return TRUE;
+ return true;
}
- return FALSE;
+ return false;
}
int
tag_op_list_append (tag_op_list_t *list,
const char *tag,
- notmuch_bool_t remove)
+ bool remove)
{
/* Make room if current array is full. This should be a fairly
* rare case, considering the initial array size.
* Is the i'th tag operation a remove?
*/
-notmuch_bool_t
+bool
tag_op_list_isremove (const tag_op_list_t *list, size_t i)
{
assert (i < list->count);
* explanatory message otherwise.
*/
const char *
-illegal_tag (const char *tag, notmuch_bool_t remove);
+illegal_tag (const char *tag, bool remove);
/*
* Create an empty list of tag operations
tag_op_list_create (void *ctx);
/*
- * Add a tag operation (delete iff remove == TRUE) to a list.
+ * Add a tag operation (delete iff remove == true) to a list.
* The list is expanded as necessary.
*/
int
tag_op_list_append (tag_op_list_t *list,
const char *tag,
- notmuch_bool_t remove);
+ bool remove);
/*
* Apply a list of tag operations, in order, to a given message.
* Is the i'th tag operation a remove?
*/
-notmuch_bool_t
+bool
tag_op_list_isremove (const tag_op_list_t *list, size_t i);
#endif
-arg-test
-corpus.mail
-hex-xcode
-parse-time
-random-corpus
-smtp-dummy
-symbol-test
-make-db-version
-test-results
-ghost-report
-tmp.*
+/arg-test
+/corpora.mail
+/hex-xcode
+/parse-time
+/random-corpus
+/smtp-dummy
+/symbol-test
+/make-db-version
+/test-results
+/ghost-report
+/tmp.*
test: all test-binaries
ifeq ($V,)
@echo 'Use "$(MAKE) V=1" to see the details for passing and known broken tests.'
- @env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
+ @env NOTMUCH_TEST_QUIET=1 $(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
else
# The user has explicitly enabled quiet execution.
ifeq ($V,0)
- @env NOTMUCH_TEST_QUIET=1 ${test_src_dir}/notmuch-test $(OPTIONS)
+ @env NOTMUCH_TEST_QUIET=1 $(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
else
- @${test_src_dir}/notmuch-test $(OPTIONS)
+ @$(NOTMUCH_SRCDIR)/$(test_src_dir)/notmuch-test $(OPTIONS)
endif
endif
As the names depend on the tests' file names, it is safe to
run the tests with this option in parallel.
---root=<dir>::
- This runs the testsuites specified under a separate directory.
- However, caution is advised, as not all tests are maintained
- with this relocation in mind, so some tests may behave
- differently.
-
- Pointing this argument at a tmpfs filesystem can improve the
- speed of the test suite for some users.
-
Certain tests require precomputed databases to complete. You can fetch these
databases with
#
test_description='the test framework itself.'
-
-################################################################
-# It appears that people try to run tests without building...
-
-if ! test -x ../notmuch
-then
- echo >&2 'You do not seem to have built notmuch yet.'
- exit 1
-fi
-
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
################################################################
# Test harness
test_begin_subtest 'test runs if prerequisite is satisfied'
test_set_prereq HAVEIT
-haveit=no
-test_expect_success 'test_have_prereq HAVEIT && haveit=yes'
+test_expect_success 'test_have_prereq HAVEIT'
test_begin_subtest 'tests clean up after themselves'
clean=no
test_begin_subtest 'failure to clean up causes the test to fail'
test_expect_code 2 'test_when_finished "(exit 2)"'
-EXPECTED=$TEST_DIRECTORY/test.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/test.expected-output
suppress_diff_date() {
sed -e 's/\(.*\-\-\- test-verbose\.4\.\expected\).*/\1/' \
-e 's/\(.*\+\+\+ test-verbose\.4\.\output\).*/\1/'
}
test_begin_subtest "Ensure that test output is suppressed unless the test fails"
-output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= ./test-verbose 2>&1 | suppress_diff_date)
+output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= $NOTMUCH_SRCDIR/test/test-verbose 2>&1 | suppress_diff_date)
expected=$(cat $EXPECTED/test-verbose-no | suppress_diff_date)
test_expect_equal "$output" "$expected"
test_begin_subtest "Ensure that -v does not suppress test output"
-output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= ./test-verbose -v 2>&1 | suppress_diff_date)
+output=$(cd $TEST_DIRECTORY; NOTMUCH_TEST_QUIET= $NOTMUCH_SRCDIR/test/test-verbose -v 2>&1 | suppress_diff_date)
expected=$(cat $EXPECTED/test-verbose-yes | suppress_diff_date)
# Do not include the results of test-verbose in totals
rm $TEST_DIRECTORY/test-results/test-verbose
#!/usr/bin/env bash
test_description="online help"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest 'notmuch --help'
test_expect_success 'notmuch --help'
#!/usr/bin/env bash
test_description='"notmuch compact"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message '[subject]=One'
add_message '[subject]=Two'
#!/usr/bin/env bash
test_description='"notmuch config"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Get string value"
test_expect_equal "$(notmuch config get user.name)" "Notmuch Test Suite"
#!/usr/bin/env bash
test_description='"notmuch setup"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Notmuch new without a config suggests notmuch setup"
output=$(notmuch --config=new-notmuch-config new 2>&1)
#!/usr/bin/env bash
test_description='"notmuch new" in several variations'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "No new messages"
output=$(NOTMUCH_NEW --debug)
set breakpoint pending on
set logging file notmuch-new-vanish-gdb.log
set logging on
-break add_file
+break notmuch_database_index_file
commands
shell rm -f ${MAIL_DIR}/vanish
continue
#!/usr/bin/env bash
test_description='"notmuch count" for messages and threads'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description='"notmuch insert"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_require_external_prereq gdb
dirname=$(dirname "$output")
test_expect_equal "$dirname" "$MAIL_DIR/Drafts/new"
+test_begin_subtest "Insert message into folder with trailing /"
+gen_insert_msg
+notmuch insert --folder=Drafts/ < "$gen_msg_filename"
+output=$(notmuch search --output=files id:${gen_msg_id})
+dirname=$(dirname "$output")
+test_expect_equal "$dirname" "$MAIL_DIR/Drafts/new"
+
test_begin_subtest "Insert message into folder, add/remove tags"
gen_insert_msg
notmuch insert --folder=Drafts +draft -unread < "$gen_msg_filename"
set breakpoint pending on
set logging file index-file-$code.log
set logging on
-break notmuch_database_add_message
+break notmuch_database_index_file
commands
return NOTMUCH_STATUS_$code
continue
gen_insert_msg
for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do
- test_begin_subtest "EXIT_FAILURE when add_message returns $code"
+ test_begin_subtest "EXIT_FAILURE when index_file returns $code"
test_expect_code 1 \
"${TEST_GDB} --batch-silent --return-child-result \
-ex 'set args insert < $gen_msg_filename' \
-x index-file-$code.gdb notmuch"
- test_begin_subtest "success exit with --keep when add_message returns $code"
+ test_begin_subtest "success exit with --keep when index_file returns $code"
test_expect_code 0 \
"${TEST_GDB} --batch-silent --return-child-result \
-ex 'set args insert --keep < $gen_msg_filename' \
done
for code in OUT_OF_MEMORY XAPIAN_EXCEPTION ; do
- test_begin_subtest "EX_TEMPFAIL when add_message returns $code"
+ test_begin_subtest "EX_TEMPFAIL when index_file returns $code"
test_expect_code 75 \
"${TEST_GDB} --batch-silent --return-child-result \
-ex 'set args insert < $gen_msg_filename' \
-x index-file-$code.gdb notmuch"
- test_begin_subtest "success exit with --keep when add_message returns $code"
+ test_begin_subtest "success exit with --keep when index_file returns $code"
test_expect_code 0 \
"${TEST_GDB} --batch-silent --return-child-result \
-ex 'set args insert --keep < $gen_msg_filename' \
#!/usr/bin/env bash
test_description='"notmuch search" in several variations'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
thread:XXX 2009-11-18 [3/3] Israel Herraiz, Keith Packard, Carl Worth; [notmuch] New to the list (inbox unread)
thread:XXX 2009-11-18 [3/3] Jan Janak, Carl Worth; [notmuch] What a great idea! (inbox unread)
thread:XXX 2009-11-18 [2/2] Jan Janak, Carl Worth; [notmuch] [PATCH] Older versions of install do not support -C. (inbox unread)
-thread:XXX 2009-11-18 [3/3] Aron Griffis, Keith Packard, Carl Worth; [notmuch] archive (inbox unread)
+thread:XXX 2009-11-18 [3/3(4)] Aron Griffis, Keith Packard, Carl Worth; [notmuch] archive (inbox unread)
thread:XXX 2009-11-18 [2/2] Keith Packard, Carl Worth; [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (inbox unread)
thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread)
thread:XXX 2009-11-18 [5/5] Mikhail Gusarov, Carl Worth, Keith Packard; [notmuch] [PATCH 1/2] Close message file after parsing message headers (inbox unread)
#!/usr/bin/env bash
test_description='various settings for "notmuch search --output="'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description='"notmuch address" in several variants'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description='"notmuch search" by folder: and path: (with variations)'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message '[dir]=bad' '[subject]="To the bone"'
add_message '[dir]=.' '[subject]="Top level"'
test_begin_subtest "Single-world folder: specification (multiple results)"
output=$(notmuch search folder:bad folder:bad/news folder:things/bad | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
-thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "Top level folder"
test_begin_subtest "Two-word path to narrow results to one"
output=$(notmuch search folder:bad/news | notmuch_search_sanitize)
-test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)"
+test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)"
test_begin_subtest "Folder search with --output=files"
output=$(notmuch search --output=files folder:bad/news | notmuch_search_files_sanitize)
# id:3wd4o8wa7fx.fsf@testarossa.amd.com
test_description='that notmuch does not overlap term positions'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message '[to]="a@b.c, x@y.z"'
#!/usr/bin/env bash
test_description='messages with unquoted . in name'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message \
'[from]="Some.Name for Someone <bugs@quoting.com>"' \
#!/usr/bin/env bash
test_description='"notmuch search" --offset and --limit parameters'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description='"notmuch search, count and show" with excludes in several variations'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# Generates a thread consisting of a top level message and 'length'
# replies. The subject of the top message 'subject: top message"
#!/usr/bin/env bash
test_description='"notmuch tag"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message '[subject]=One'
add_message '[subject]=Two'
#!/usr/bin/env bash
test_description="--format=json output"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Show message: json"
add_message "[subject]=\"json-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"json-show-message\""
emacs_fcc_message \
"$subject" \
'This is a test message with inline attachment with a filename' \
- "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
+ "(mml-attach-file \"$NOTMUCH_SRCDIR/test/README\" nil nil \"inline\")
(message-goto-eoh)
(insert \"Message-ID: <$id>\n\")"
output=$(notmuch show --format=json "id:$id")
filename=$(notmuch search --output=files "id:$id")
# Get length of README after base64-encoding, minus additional newline.
-attachment_length=$(( $(base64 $TEST_DIRECTORY/README | wc -c) - 1 ))
+attachment_length=$(( $(base64 $NOTMUCH_SRCDIR/test/README | wc -c) - 1 ))
test_expect_equal_json "$output" "[[[{\"id\": \"$id\", \"match\": true, \"excluded\": false, \"filename\": [\"$filename\"], \"timestamp\": 946728000, \"date_relative\": \"2000-01-01\", \"tags\": [\"inbox\"], \"headers\": {\"Subject\": \"$subject\", \"From\": \"Notmuch Test Suite <test_suite@notmuchmail.org>\", \"To\": \"test_suite@notmuchmail.org\", \"Date\": \"Sat, 01 Jan 2000 12:00:00 +0000\"}, \"body\": [{\"id\": 1, \"content-type\": \"multipart/mixed\", \"content\": [{\"id\": 2, \"content-type\": \"text/plain\", \"content\": \"This is a test message with inline attachment with a filename\"}, {\"id\": 3, \"content-type\": \"application/octet-stream\", \"content-length\": $attachment_length, \"content-transfer-encoding\": \"base64\", \"content-disposition\": \"inline\", \"filename\": \"README\"}]}]}, []]]]"
test_begin_subtest "Search message: json, utf-8"
test_expect_code 21 "notmuch search --format-version=999 \\*"
test_begin_subtest "Show message: multiple filenames"
-add_message "[id]=message-id@example.com [filename]=copy1"
-add_message "[id]=message-id@example.com [filename]=copy2"
+add_message '[id]=message-id@example.com [filename]=copy1 [date]="Fri, 05 Jan 2001 15:43:52 +0000"'
+add_message '[id]=message-id@example.com [filename]=copy2 [date]="Fri, 05 Jan 2001 15:43:52 +0000"'
cat <<EOF > EXPECTED
[
[
#!/usr/bin/env bash
test_description="--format=sexp output"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Show message: sexp"
add_message "[subject]=\"sexp-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[bcc]=\"test_suite+bcc@notmuchmail.org\"" "[reply-to]=\"test_suite+replyto@notmuchmail.org\"" "[body]=\"sexp-show-message\""
output=$(notmuch show --format=sexp "jsön-show-méssage")
test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :filename (\"${gen_msg_filename}\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\" \"unread\") :headers (:Subject \"sexp-show-utf8-body-sübjéct\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"text/plain\" :content \"jsön-show-méssage\n\"))) ())))"
+test_begin_subtest "Search message: sexp, utf-8"
+add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
+output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
+test_expect_equal "$output" "((:thread \"0000000000000004\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
+
test_begin_subtest "Show message: sexp, inline attachment filename"
subject='sexp-show-inline-attachment-filename'
id="sexp-show-inline-attachment-filename@notmuchmail.org"
emacs_fcc_message \
"$subject" \
'This is a test message with inline attachment with a filename' \
- "(mml-attach-file \"$TEST_DIRECTORY/README\" nil nil \"inline\")
+ "(mml-attach-file \"$NOTMUCH_SRCDIR/test/README\" nil nil \"inline\")
(message-goto-eoh)
(insert \"Message-ID: <$id>\n\")"
output=$(notmuch show --format=sexp "id:$id")
filename=$(notmuch search --output=files "id:$id")
# Get length of README after base64-encoding, minus additional newline.
-attachment_length=$(( $(base64 $TEST_DIRECTORY/README | wc -c) - 1 ))
+attachment_length=$(( $(base64 $NOTMUCH_SRCDIR/test/README | wc -c) - 1 ))
test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename (\"$filename\") :timestamp 946728000 :date_relative \"2000-01-01\" :tags (\"inbox\") :headers (:Subject \"sexp-show-inline-attachment-filename\" :From \"Notmuch Test Suite <test_suite@notmuchmail.org>\" :To \"test_suite@notmuchmail.org\" :Date \"Sat, 01 Jan 2000 12:00:00 +0000\") :body ((:id 1 :content-type \"multipart/mixed\" :content ((:id 2 :content-type \"text/plain\" :content \"This is a test message with inline attachment with a filename\") (:id 3 :content-type \"application/octet-stream\" :content-disposition \"inline\" :filename \"README\" :content-transfer-encoding \"base64\" :content-length $attachment_length))))) ())))"
-test_begin_subtest "Search message: sexp, utf-8"
-add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""
-output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
-test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"
-
-
test_done
#!/usr/bin/env bash
test_description="--format=text output"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Show message: text"
add_message "[subject]=\"text-show-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"text-show-message\""
#!/usr/bin/env bash
test_description="output of multipart message"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
cat <<EOF > embedded_message_body
Content-Type: multipart/alternative; boundary="==-=-=="
#!/usr/bin/env bash
test_description="naming of threads with changing subject"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Initial thread name (oldest-first search)"
add_message '[subject]="thread-naming: Initial thread subject"' \
#!/usr/bin/env bash
test_description="naming of authors with unusual addresses"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Add author with empty quoted real name"
add_message '[subject]="author-naming: Initial thread subject"' \
#!/usr/bin/env bash
test_description='notmuch show --format=raw'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message
add_message
#!/usr/bin/env bash
test_description="\"notmuch reply\" in several variations"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Basic reply"
add_message '[from]="Sender <sender@example.com>"' \
#!/usr/bin/env bash
test_description="\"notmuch reply --reply-to=sender\" in several variations"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Basic reply-to-sender"
add_message '[from]="Sender <sender@example.com>"' \
#!/usr/bin/env bash
test_description="\"notmuch dump\" and \"notmuch restore\""
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
NOTMUCH_NEW > /dev/null
test_begin_subtest "dump header"
#!/usr/bin/env bash
test_description="handling of uuencoded data"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message [subject]=uuencodetest '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' \
'[body]="This message is used to ensure that notmuch correctly handles a
#!/usr/bin/env bash
test_description="threading when messages received out of order"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# Generate all single-root four message thread structures. We'll use
# this for multiple tests below.
-THREADS=$($NOTMUCH_PYTHON ${TEST_DIRECTORY}/gen-threads.py 4)
+THREADS=$($NOTMUCH_PYTHON ${NOTMUCH_SRCDIR}/test/gen-threads.py 4)
nthreads=$(wc -l <<< "$THREADS")
test_begin_subtest "Messages with one parent get linked in all delivery orders"
#!/usr/bin/env bash
test_description="author reordering;"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Adding parent message"
generate_message [body]=findme [id]=new-parent-id [subject]=author-reorder-threadtest '[from]="User <user@example.com>"' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"'
#!/usr/bin/env bash
test_description="From line heuristics (with multiple configured addresses)"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Magic from guessing (nothing to go on)"
add_message '[from]="Sender <sender@example.com>"' \
#!/usr/bin/env bash
test_description="messages with ridiculously-long message IDs"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Referencing long ID before adding"
generate_message '[subject]="Reference of ridiculously-long message ID"' \
#!/usr/bin/env bash
test_description="encoding issues"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Message with text of unknown charset"
add_message '[content-type]="text/plain; charset=unknown-8bit"' \
#!/usr/bin/env bash
test_description="emacs interface"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
-EXPECTED=$TEST_DIRECTORY/emacs.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
add_email_corpus
# syntax errors in test-lib.el cause mysterious failures
test_begin_subtest "Syntax of emacs test library"
-test_expect_success "${TEST_EMACS} -Q --batch --load $TEST_DIRECTORY/test-lib.el"
+test_expect_success "${TEST_EMACS} -Q --batch --load $NOTMUCH_SRCDIR/test/test-lib.el"
test_begin_subtest "Basic notmuch-hello view in emacs"
test_emacs '(notmuch-hello)
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "Reply within emacs to a message with TAB in subject"
+test_emacs '(let ((message-hidden-headers ''()))
+ (notmuch-search "id:1258471718-6781-1-git-send-email-dottedmag@dottedmag.net")
+ (notmuch-test-wait)
+ (notmuch-search-show-thread)
+ (notmuch-test-wait)
+ (notmuch-show-reply-sender)
+ (test-output))'
+sed -i -e 's/^In-Reply-To: <.*>$/In-Reply-To: <XXX>/' OUTPUT
+sed -i -e 's/^References: <.*>$/References: <XXX>/' OUTPUT
+sed -i -e '/^--text follows this line--$/q' OUTPUT
+cat <<EOF >EXPECTED
+From: Notmuch Test Suite <test_suite@notmuchmail.org>
+To: Mikhail Gusarov <dottedmag@dottedmag.net>
+Subject: Re: [notmuch] [PATCH 1/2] Close message file after parsing message headers
+In-Reply-To: <XXX>
+Fcc: ${MAIL_DIR}/sent
+References: <XXX>
+--text follows this line--
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_begin_subtest "Reply from alternate address within emacs"
add_message '[from]="Sender <sender@example.com>"' \
[to]=test_suite_other@notmuchmail.org
#!/usr/bin/env bash
test_description="Emacs with large search results buffer"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
x=xxxxxxxxxx # 10
x=$x$x$x$x$x$x$x$x$x$x # 100
#!/usr/bin/env bash
test_description="emacs: mail subject to filename"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# emacs server can't be started in a child process with $(test_emacs ...)
test_emacs '(ignore)' > /dev/null
test_description="maildir synchronization"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# Create the expected maildir structure
mkdir $MAIL_DIR/cur
output=$(notmuch search subject:"Adding message with S" | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Adding message with S (inbox)"
+test_begin_subtest "Adding message with 'S' w/o 'unread' in new.tags prevents 'unread' tag"
+OLDCONFIG=$(notmuch config get new.tags)
+notmuch config set new.tags "inbox"
+add_message [subject]='"Adding message with S 2"' [filename]='adding-with-s-flag2:2,S' [dir]=cur
+notmuch config set new.tags $OLDCONFIG
+output=$(notmuch search subject:Adding-message-with-S-2 | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Adding message with S 2 (inbox)"
+
test_begin_subtest "Adding 'replied' tag adds 'R' flag to filename"
add_message [subject]='"Adding replied tag"' [filename]='adding-replied-tag:2,S' [dir]=cur
notmuch tag +replied subject:"Adding replied tag"
"Date": "GENERATED_DATE"},
"body": [{"id": 1,
"content-type": "text/plain",
-"content": "This is just a test message (#3)\n"}]},
+"content": "This is just a test message (#4)\n"}]},
[]]]]'
test_begin_subtest "notmuch reply works with renamed file (without notmuch new)"
NOTMUCH_NEW > output
notmuch search subject:"Duplicated message" | notmuch_search_sanitize >> output
test_expect_equal "$(< output)" "No new mail.
-thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Duplicated message (inbox replied)"
+thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Duplicated message (inbox replied)"
test_begin_subtest "Adding duplicate message without flags does not remove tags"
cp "$MAIL_DIR/cur/duplicated-message-copy:2,RS" "$MAIL_DIR/cur/duplicated-message-another-copy:2,"
NOTMUCH_NEW > output
notmuch search subject:"Duplicated message" | notmuch_search_sanitize >> output
test_expect_equal "$(< output)" "No new mail.
-thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Duplicated message (inbox replied)"
+thread:XXX 2001-01-05 [1/1(3)] Notmuch Test Suite; Duplicated message (inbox replied)"
test_begin_subtest "Tag changes modify flags of multiple files"
notmuch tag -replied subject:"Duplicated message"
test_begin_subtest "Files in new/ get default synchronized tags"
OLDCONFIG=$(notmuch config get new.tags)
-notmuch config set new.tags test
+notmuch config set new.tags "test;unread"
add_message [subject]='"File in new/"' [dir]=new [filename]='file-in-new'
notmuch config set new.tags $OLDCONFIG
notmuch search 'subject:"File in new"' | notmuch_search_sanitize > output
test_expect_equal "$(< output)" \
"thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; File in new/ (test unread)"
+for tag in draft flagged passed replied; do
+ test_begin_subtest "$tag is valid in new.tags"
+ OLDCONFIG=$(notmuch config get new.tags)
+ notmuch config set new.tags "$tag;unread"
+ add_message [subject]="\"$tag sync in new\"" [dir]=new
+ notmuch config set new.tags $OLDCONFIG
+ notmuch search "subject:\"$tag sync in new\"" | notmuch_search_sanitize > output
+ test_expect_equal "$(< output)" \
+ "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; $tag sync in new ($tag unread)"
+done
test_done
# - verification of signatures from expired/revoked keys
test_description='PGP/MIME signature verification and decryption'
-. ./test-lib.sh || exit 1
-
-add_gnupg_home ()
-{
- local output
- [ -d ${GNUPGHOME} ] && return
- _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
- at_exit_function _gnupg_exit
- mkdir -m 0700 "$GNUPGHOME"
- gpg --no-tty --import <$TEST_DIRECTORY/gnupg-secret-key.asc >"$GNUPGHOME"/import.log 2>&1
- test_debug "cat $GNUPGHOME/import.log"
- if (gpg --quick-random --version >/dev/null 2>&1) ; then
- echo quick-random >> "$GNUPGHOME"/gpg.conf
- elif (gpg --debug-quick-random --version >/dev/null 2>&1) ; then
- echo debug-quick-random >> "$GNUPGHOME"/gpg.conf
- fi
- echo no-emit-version >> "$GNUPGHOME"/gpg.conf
-}
+. $(dirname "$0")/test-lib.sh || exit 1
##################################################
"This is a test signed message." \
"(mml-secure-message-sign)"'
+test_begin_subtest "signed part content-type indexing"
+output=$(notmuch search mimetype:multipart/signed and mimetype:application/pgp-signature | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test signed message 001 (inbox signed)"
+
test_begin_subtest "signature verification"
output=$(notmuch show --format=json --verify subject:"test signed message 001" \
| notmuch_json_show_sanitize \
"This is a test encrypted message.\n" \
"(mml-attach-file \"TESTATTACHMENT\") (mml-secure-message-encrypt)"'
+test_begin_subtest "encrypted part content-type indexing"
+output=$(notmuch search mimetype:multipart/encrypted and mimetype:application/pgp-encrypted and mimetype:application/octet-stream | notmuch_search_sanitize)
+test_expect_equal "$output" "thread:XXX 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message 001 (encrypted inbox)"
+
test_begin_subtest "decryption, --format=text"
output=$(notmuch show --format=text --decrypt subject:"test encrypted message 001" \
| notmuch_show_sanitize_all \
test_begin_subtest "reply to encrypted message"
output=$(notmuch reply --decrypt subject:"test encrypted message 002" \
- | grep -v -e '^In-Reply-To:' -e '^References:')
+ | notmuch_drop_mail_headers In-Reply-To References)
expected='From: Notmuch Test Suite <test_suite@notmuchmail.org>
Subject: Re: test encrypted message 002
#!/usr/bin/env bash
test_description='S/MIME signature verification and decryption'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_gpgsm_home ()
{
_gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
at_exit_function _gnupg_exit
mkdir -m 0700 "$GNUPGHOME"
- gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $TEST_DIRECTORY/smime/test.crt >"$GNUPGHOME"/import.log 2>&1
+ gpgsm --no-tty --no-common-certs-import --disable-dirmngr --import < $NOTMUCH_SRCDIR/test/smime/test.crt >"$GNUPGHOME"/import.log 2>&1
fpr=$(gpgsm --list-key test_suite@notmuchmail.org | sed -n 's/.*fingerprint: //p')
echo "$fpr S relax" >> $GNUPGHOME/trustlist.txt
test_debug "cat $GNUPGHOME/import.log"
test_require_external_prereq openssl
test_require_external_prereq gpgsm
-cp $TEST_DIRECTORY/smime/key+cert.pem test_suite.pem
+cp $NOTMUCH_SRCDIR/test/smime/key+cert.pem test_suite.pem
FINGERPRINT=$(openssl x509 -fingerprint -in test_suite.pem -noout | sed -e 's/^.*=//' -e s/://g)
test_begin_subtest "Signature verification (openssl)"
notmuch show --format=raw subject:"test signed message 001" |\
- openssl smime -verify -CAfile $TEST_DIRECTORY/smime/test.crt 2>OUTPUT
+ openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT
cat <<EOF > EXPECTED
Verification successful
EOF
test_begin_subtest "Decryption and signature verification (openssl)"
notmuch show --format=raw subject:"test encrypted message 001" |\
openssl smime -decrypt -recip test_suite.pem |\
- openssl smime -verify -CAfile $TEST_DIRECTORY/smime/test.crt 2>OUTPUT
+ openssl smime -verify -CAfile $NOTMUCH_SRCDIR/test/smime/test.crt 2>OUTPUT
cat <<EOF > EXPECTED
Verification successful
EOF
--- /dev/null
+#!/usr/bin/env bash
+
+# TODO: test index.decryption=failed
+
+test_description='indexing decrypted mail'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+##################################################
+
+add_gnupg_home
+# get key fingerprint
+FINGERPRINT=$(gpg --no-tty --list-secret-keys --with-colons --fingerprint | grep '^fpr:' | cut -d: -f10)
+
+# create a test encrypted message
+test_begin_subtest 'emacs delivery of encrypted message'
+test_expect_success \
+'emacs_fcc_message \
+ "test encrypted message for cleartext index 001" \
+ "This is a test encrypted message with a wumpus.\n" \
+ "(mml-secure-message-encrypt)"'
+
+test_begin_subtest "search for unindexed cleartext"
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# create a test encrypted message that is indexed in the clear
+test_begin_subtest 'emacs delivery of encrypted message'
+test_expect_success \
+'emacs_fcc_message --try-decrypt=true \
+ "test encrypted message for cleartext index 002" \
+ "This is a test encrypted message with a wumpus.\n" \
+ "(mml-secure-message-encrypt)"'
+
+test_begin_subtest "emacs delivery of encrypted message, indexed cleartext"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000002 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox)'
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property for one message"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+
+test_begin_subtest "message should go away after deletion"
+# cache the message in an env var and remove it:
+fname=$(notmuch search --output=files wumpus)
+contents="$(notmuch show --format=raw wumpus)"
+rm -f "$fname"
+notmuch new
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# try reinserting it without decryption, should stay the same:
+test_begin_subtest "message cleartext not present after insert"
+notmuch insert --folder=sent <<<"$contents"
+output=$(notmuch search wumpus)
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# try reinserting it with decryption, should appear again, but now we
+# have two copies of the message:
+test_begin_subtest "message cleartext is present after reinserting with --try-decrypt"
+notmuch insert --folder=sent --try-decrypt <<<"$contents"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000003 2000-01-01 [1/1(2)] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# remove all copies
+test_begin_subtest "delete all copies of the message"
+mid="$(notmuch search --output=messages wumpus)"
+rm -f $(notmuch search --output=files wumpus)
+notmuch new
+output=$(notmuch search "id:$mid")
+expected=''
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# try inserting it with decryption, should appear as a single copy
+# (note: i think thread id skips 4 because of duplicate message-id
+# insertion, above)
+test_begin_subtest "message cleartext is present with insert --try-decrypt"
+notmuch insert --folder=sent --try-decrypt <<<"$contents"
+output=$(notmuch search wumpus)
+expected='thread:0000000000000005 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (encrypted inbox unread)'
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+
+# add a tag to all messages to ensure that it stays after reindexing
+test_begin_subtest 'tagging all messages'
+test_expect_success 'notmuch tag +blarney "encrypted message"'
+test_begin_subtest "verify that tags have not changed"
+output=$(notmuch search tag:blarney)
+expected='thread:0000000000000001 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)
+thread:0000000000000005 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox unread)'
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# see if first message shows up after reindexing with --try-decrypt=true (same $expected, untouched):
+test_begin_subtest 'reindex old messages'
+test_expect_success 'notmuch reindex --try-decrypt=true tag:encrypted and not property:index.decryption=success'
+test_begin_subtest "reindexed encrypted message, including cleartext"
+output=$(notmuch search wumpus)
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property for both messages"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+
+# try to remove cleartext indexing
+test_begin_subtest 'reindex without cleartext'
+test_expect_success 'notmuch reindex tag:encrypted and property:index.decryption=success'
+test_begin_subtest "reindexed encrypted messages, without cleartext"
+output=$(notmuch search wumpus)
+expected=''
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# and the same search, but by property ($expected is untouched):
+test_begin_subtest "emacs search by property with both messages unindexed"
+output=$(notmuch search property:index.decryption=success)
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+# ensure that the tags remain even when we are dropping the cleartext.
+test_begin_subtest "verify that tags remain without cleartext"
+output=$(notmuch search tag:blarney)
+expected='thread:0000000000000001 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 001 (blarney encrypted inbox)
+thread:0000000000000005 2000-01-01 [1/1] Notmuch Test Suite; test encrypted message for cleartext index 002 (blarney encrypted inbox unread)'
+test_expect_equal \
+ "$output" \
+ "$expected"
+
+
+# TODO: test removal of a message from the message store between
+# indexing and reindexing.
+
+# TODO: insert the same message into the message store twice, index,
+# remove one of them from the message store, and then reindex.
+# reindexing should return a failure but the message should still be
+# present? -- or what should the semantics be if you ask to reindex a
+# message whose underlying files have been renamed or moved or
+# removed?
+
+test_done
test_description='exception symbol hiding'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest 'running test' run_test
mkdir -p ${PWD}/fakedb/.notmuch
test_expect_equal "$result" "$output"
test_begin_subtest 'comparing existing to exported symbols'
-nm -P $TEST_DIRECTORY/../lib/libnotmuch.so | awk '$2 == "T" && $1 ~ "^notmuch" {print $1}' | sort | uniq > ACTUAL
-sed -n 's/^\(notmuch_[a-zA-Z0-9_]*\)[[:blank:]]*(.*/\1/p' $TEST_DIRECTORY/../lib/notmuch.h | sort | uniq > EXPORTED
+nm -P $NOTMUCH_BUILDDIR/lib/libnotmuch.so | awk '$2 == "T" && $1 ~ "^notmuch" {print $1}' | sort | uniq > ACTUAL
+sed -n 's/^\(notmuch_[a-zA-Z0-9_]*\)[[:blank:]]*(.*/\1/p' $NOTMUCH_SRCDIR/lib/notmuch.h | sort | uniq > EXPORTED
test_expect_equal_file EXPORTED ACTUAL
test_done
#!/usr/bin/env bash
test_description='folder tags removed and added through file renames remain consistent'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "No new messages"
output=$(NOTMUCH_NEW)
test_begin_subtest "Test matches folder:spam"
output=$(notmuch search folder:spam)
-test_expect_equal "$output" "thread:0000000000000001 2001-01-05 [1/1] Notmuch Test Suite; Single new message (inbox unread)"
+test_expect_equal "$output" "thread:0000000000000001 2001-01-05 [1/1(2)] Notmuch Test Suite; Single new message (inbox unread)"
test_begin_subtest "Remove folder:spam copy of email"
rm $dir/spam/$(basename $file_x)
#!/usr/bin/env bash
test_description='atomicity'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# This script tests the effects of killing and restarting "notmuch
# new" at arbitrary points. If notmuch new is properly atomic, the
# -tty /dev/null works around a conflict between the 'timeout' wrapper
# and gdb's attempt to control the TTY.
export MAIL_DIR
- ${TEST_GDB} -tty /dev/null -batch -x $TEST_DIRECTORY/atomicity.py notmuch 1>gdb.out 2>&1
+ ${TEST_GDB} -tty /dev/null -batch -x $NOTMUCH_SRCDIR/test/atomicity.py notmuch 1>gdb.out 2>&1
# Get the final, golden output
notmuch search '*' > expected
#!/usr/bin/env bash
test_description="python bindings"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_require_external_prereq ${NOTMUCH_PYTHON}
test_begin_subtest "output of count matches test code"
notmuch count --lastmod '*' | cut -f2-3 > OUTPUT
test_expect_equal_file INITIAL_OUTPUT OUTPUT
+add_message '[content-type]="text/plain; charset=iso-8859-2"' \
+ '[content-transfer-encoding]=8bit' \
+ '[subject]="ISO-8859-2 encoded message"' \
+ "[body]=$'Czech word tu\350\362\341\350\350\355 means pinguin\'s.'" # ISO-8859-2 characters are generated by shell's escape sequences
+test_begin_subtest "Add ISO-8859-2 encoded message, call get_message_parts"
+test_python <<EOF
+import notmuch
+db = notmuch.Database(mode=notmuch.Database.MODE.READ_ONLY)
+q_new = notmuch.Query(db, 'ISO-8859-2 encoded message')
+for m in q_new.search_messages():
+ for mp in m.get_message_parts():
+ continue
+ print(m.get_message_id())
+EOF
+
+notmuch search --sort=oldest-first --output=messages "tučňáččí" | sed s/^id:// > EXPECTED
+test_expect_equal_file EXPECTED OUTPUT
test_done
#!/usr/bin/env bash
test_description="ruby bindings"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
if [ "${NOTMUCH_HAVE_RUBY_DEV}" = "0" ]; then
test_subtest_missing_external_prereq_["ruby development files"]=t
#!/usr/bin/env bash
test_description='hooks'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
HOOK_DIR=${MAIL_DIR}/.notmuch/hooks
#!/usr/bin/env bash
test_description="argument parsing"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "sanity check"
-$TEST_DIRECTORY/arg-test pos1 --keyword=one --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
flags 5
int 7
EOF
test_expect_equal_file EXPECTED OUTPUT
+test_begin_subtest "sanity check zero values"
+$TEST_DIRECTORY/arg-test --keyword=zero --boolean=false --int=0 > OUTPUT
+cat <<EOF > EXPECTED
+boolean 0
+keyword 0
+int 0
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "space instead of = between parameter name and value"
+# Note: spaces aren't allowed for booleans. false turns into a positional arg!
+$TEST_DIRECTORY/arg-test --keyword one --boolean false --string foo --int 7 --flag one --flag three > OUTPUT
+cat <<EOF > EXPECTED
+boolean 1
+keyword 1
+flags 5
+int 7
+string foo
+positional arg 1 false
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
test_done
#!/usr/bin/env bash
test_description="emacs test function sanity"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "emacs test function sanity"
test_emacs_expect_t 't'
#!/usr/bin/env bash
test_description="emacs address cleaning"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "notmuch-test-address-clean part 1"
test_emacs_expect_t '(notmuch-test-address-cleaning-1)'
#!/usr/bin/env bash
test_description="emacs notmuch-hello view"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
-EXPECTED=$TEST_DIRECTORY/emacs.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs.expected-output
add_email_corpus
#!/usr/bin/env bash
test_description="emacs notmuch-show view"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
-EXPECTED=$TEST_DIRECTORY/emacs-show.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/emacs-show.expected-output
add_email_corpus
#!/usr/bin/env bash
test_description="emacs notmuch-show charset handling"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
UTF8_YEN=$'\xef\xbf\xa5'
#!/usr/bin/env bash
test_description="emacs tree view interface"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
-EXPECTED=$TEST_DIRECTORY/tree.expected-output
+EXPECTED=$NOTMUCH_SRCDIR/test/tree.expected-output
add_email_corpus
#!/usr/bin/env bash
test_description='messages with missing headers'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# Notmuch requires at least one of from, subject, or to or it will
# ignore the file. Generate two messages so that together they cover
#!/usr/bin/env bash
test_description="hex encoding and decoding"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "round trip"
-find $TEST_DIRECTORY/corpus -type f -print | sort | xargs cat > EXPECTED
+find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
$TEST_DIRECTORY/hex-xcode --direction=encode < EXPECTED | $TEST_DIRECTORY/hex-xcode --direction=decode > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_expect_equal_file EXPECTED.$test_count OUTPUT.$test_count
test_begin_subtest "round trip (in-place)"
-find $TEST_DIRECTORY/corpus -type f -print | sort | xargs cat > EXPECTED
+find $NOTMUCH_SRCDIR/test/corpora/default -type f -print | sort | xargs cat > EXPECTED
$TEST_DIRECTORY/hex-xcode --in-place --direction=encode < EXPECTED |\
$TEST_DIRECTORY/hex-xcode --in-place --direction=decode > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
#!/usr/bin/env bash
test_description="date/time parser module"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# Sanity/smoke tests for the date/time parser independent of notmuch
#!/usr/bin/env bash
test_description="date:since..until queries"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
cat <<EOF >EXPECTED
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] Carl Worth| Aron Griffis, Keith Packard; [notmuch] archive (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)
EOF
test_expect_equal_file EXPECTED OUTPUT
# database is constructed properly, even in the presence of
# non-RFC-compliant headers'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "Use References when In-Reply-To is broken"
add_message '[id]="foo@one.com"' \
#!/usr/bin/env bash
test_description='"notmuch show"'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description="database upgrade"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
dbtarball=database-v1.tar.xz
#!/usr/bin/env bash
test_description="database version and feature compatibility"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest "future database versions abort open"
${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 9999 ""
#!/usr/bin/env bash
test_description="error reporting for library"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
if (stat != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "error opening database: %d\n", stat);
}
- stat = notmuch_database_add_message (db, "/dev/null", NULL);
+ stat = notmuch_database_index_file (db, "/dev/null", NULL, NULL);
if (stat)
fputs (notmuch_database_status_string (db), stderr);
if (stat != NOTMUCH_STATUS_SUCCESS) {
fprintf (stderr, "error opening database: %d\n", stat);
}
- stat = notmuch_database_add_message (db, "./nonexistent", NULL);
+ stat = notmuch_database_index_file (db, "./nonexistent", NULL, NULL);
if (stat) {
char *status_string = notmuch_database_status_string (db);
if (status_string) fputs (status_string, stderr);
#!/usr/bin/env bash
test_description="database revision tracking"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
test_description='test of searching by thread-id'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description="library config API"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
# works properly and attempted fixes to threading issues do not break
# the expected contents of the index.
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
message_a() {
mkdir -p ${MAIL_DIR}/cur
test_ghost_count() {
test_begin_subtest "${2:-Expecting $1 ghosts(s)}"
- ghosts=$(../ghost-report ${MAIL_DIR}/.notmuch/xapian)
+ ghosts=$($NOTMUCH_BUILDDIR/test/ghost-report ${MAIL_DIR}/.notmuch/xapian)
test_expect_equal "$ghosts" "$1"
}
test_begin_subtest 'No ghosts should remain after deletion of second message'
# this is known to fail; we are leaking ghost messages deliberately
test_subtest_known_broken
-ghosts=$(../ghost-report ${MAIL_DIR}/.notmuch/xapian)
+ghosts=$($NOTMUCH_BUILDDIR/test/ghost-report ${MAIL_DIR}/.notmuch/xapian)
test_expect_equal "$ghosts" "0"
rm -f ${MAIL_DIR}/cur/a
#!/usr/bin/env bash
test_description='named queries'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
QUERYSTR="date:2009-11-18..2009-11-18 and tag:unread"
#!/usr/bin/env bash
test_description="message property API"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description="locking"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
if [ "${NOTMUCH_HAVE_XAPIAN_DB_RETRY_LOCK}" = "0" ]; then
test_subtest_missing_external_prereq_["lock retry support"]=t
#!/usr/bin/env bash
test_description="Emacs Draft Handling"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus
#!/usr/bin/env bash
test_description="DatabaseModifiedError handling"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
# add enough messages to trigger the exception
add_email_corpus
#!/usr/bin/env bash
test_description='regular expression searches'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
if [ $NOTMUCH_HAVE_XAPIAN_FIELD_PROCESSOR -eq 0 ]; then
test_done
test_begin_subtest "unanchored folder:// specification"
output=$(notmuch search folder:/bad/ | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
-thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "anchored folder:// search"
test_begin_subtest "unanchored path:// specification"
output=$(notmuch search path:/bad/ | notmuch_search_sanitize)
test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; To the bone (inbox unread)
-thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bears (inbox unread)
+thread:XXX 2001-01-05 [1/1(2)] Notmuch Test Suite; Bears (inbox unread)
thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; Bites, stings, sad feelings (inbox unread)"
test_begin_subtest "anchored path:// search"
#!/usr/bin/env bash
test_description="parsing of bad dates"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message [date]='"()"'
--- /dev/null
+#!/usr/bin/env bash
+test_description="duplicate message ids"
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_message '[id]="duplicate"' '[subject]="message 1" [filename]=copy1'
+add_message '[id]="duplicate"' '[subject]="message 2" [filename]=copy2'
+
+add_message '[id]="duplicate"' '[subject]="message 0" [filename]=copy0'
+test_begin_subtest 'search: first indexed subject preserved'
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1(3)] Notmuch Test Suite; message 1 (inbox unread)
+EOF
+notmuch search id:duplicate | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'First subject preserved in notmuch-show (json)'
+test_subtest_known_broken
+output=$(notmuch show --body=false --format=json id:duplicate | notmuch_json_show_sanitize)
+expected='[[[{
+ "id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": [
+ "'"${MAIL_DIR}"/copy0'",
+ "'"${MAIL_DIR}"/copy1'",
+ "'"${MAIL_DIR}"/copy2'"
+ ],
+ "timestamp": 42,
+ "date_relative": "2001-01-05",
+ "tags": ["inbox","unread"],
+ "headers": {
+ "Subject": "message 1",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "GENERATED_DATE"
+ }
+ },
+[]]]]'
+test_expect_equal_json "$output" "$expected"
+
+test_begin_subtest 'Search for second subject'
+cat <<EOF >EXPECTED
+MAIL_DIR/copy0
+MAIL_DIR/copy1
+MAIL_DIR/copy2
+EOF
+notmuch search --output=files subject:'"message 2"' | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+add_message '[id]="duplicate"' '[body]="sekrit" [filename]=copy3'
+test_begin_subtest 'search for body in duplicate file'
+cat <<EOF >EXPECTED
+MAIL_DIR/copy0
+MAIL_DIR/copy1
+MAIL_DIR/copy2
+MAIL_DIR/copy3
+EOF
+notmuch search --output=files "sekrit" | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+rm ${MAIL_DIR}/copy3
+test_begin_subtest 'reindex drops terms in duplicate file'
+cp /dev/null EXPECTED
+notmuch reindex '*'
+notmuch search --output=files "sekrit" | notmuch_dir_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex choses subject from first filename'
+cat <<EOF > EXPECTED
+thread:XXX 2001-01-05 [1/1(3)] Notmuch Test Suite; message 0 (inbox unread)
+EOF
+notmuch search id:duplicate | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+rm ${MAIL_DIR}/copy0
+test_begin_subtest 'Deleted first duplicate file does not stop notmuch show from working'
+output=$(notmuch show --body=false --format=json id:duplicate |
+ notmuch_json_show_sanitize | sed 's/message [0-9]/A_SUBJECT/')
+expected='[[[{
+ "id": "XXXXX",
+ "match": true,
+ "excluded": false,
+ "filename": [
+ "'"${MAIL_DIR}"/copy0'",
+ "'"${MAIL_DIR}"/copy1'",
+ "'"${MAIL_DIR}"/copy2'"
+ ],
+ "timestamp": 42,
+ "date_relative": "2001-01-05",
+ "tags": ["inbox","unread"],
+ "headers": {
+ "Subject": "A_SUBJECT",
+ "From": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "To": "Notmuch Test Suite <test_suite@notmuchmail.org>",
+ "Date": "GENERATED_DATE"
+ }
+ },
+[]]]]'
+
+test_expect_equal_json "$output" "$expected"
+
+test_done
#!/usr/bin/env bash
test_description="indexing of html parts"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_email_corpus html
#!/usr/bin/env bash
test_description="command line arguments"
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
add_message
--- /dev/null
+#!/usr/bin/env bash
+test_description='reindexing messages'
+. $(dirname "$0")/test-lib.sh || exit 1
+
+add_email_corpus
+
+notmuch tag +usertag1 '*'
+
+notmuch search '*' | notmuch_search_sanitize > initial-threads
+notmuch search --output=messages '*' > initial-message-ids
+notmuch dump > initial-dump
+
+test_begin_subtest 'reindex preserves threads'
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file initial-threads OUTPUT
+
+test_begin_subtest 'reindex after removing duplicate file preserves threads'
+# remove one copy
+sed 's,3/3(4),3/3,' < initial-threads > EXPECTED
+mv $MAIL_DIR/bar/18:2, duplicate-msg-1.eml
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex preserves message-ids'
+notmuch reindex '*'
+notmuch search --output=messages '*' > OUTPUT
+test_expect_equal_file initial-message-ids OUTPUT
+
+test_begin_subtest 'reindex preserves tags'
+notmuch reindex '*'
+notmuch dump > OUTPUT
+test_expect_equal_file initial-dump OUTPUT
+
+test_begin_subtest 'reindex moves a message between threads'
+notmuch search --output=threads id:87iqd9rn3l.fsf@vertex.dottedmag > EXPECTED
+# re-parent
+sed -i 's/1258471718-6781-1-git-send-email-dottedmag@dottedmag.net/87iqd9rn3l.fsf@vertex.dottedmag/' $MAIL_DIR/02:2,*
+notmuch reindex id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net
+notmuch search --output=threads id:1258471718-6781-2-git-send-email-dottedmag@dottedmag.net > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest 'reindex detects removal of all files'
+notmuch search --output=messages not id:20091117232137.GA7669@griffis1.net> EXPECTED
+# remove both copies
+mv $MAIL_DIR/cur/51:2,* duplicate-message-2.eml
+notmuch reindex id:20091117232137.GA7669@griffis1.net
+notmuch search --output=messages '*' > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_begin_subtest "reindex preserves properties"
+cat <<EOF > prop-dump
+#= 1258471718-6781-1-git-send-email-dottedmag@dottedmag.net userprop=userval
+#= 1258471718-6781-2-git-send-email-dottedmag@dottedmag.net userprop=userval
+#= 1258491078-29658-1-git-send-email-dottedmag@dottedmag.net userprop=userval1
+#= 20091117190054.GU3165@dottiness.seas.harvard.edu userprop=userval
+#= 20091117203301.GV3165@dottiness.seas.harvard.edu userprop=userval3
+#= 87fx8can9z.fsf@vertex.dottedmag userprop=userval2
+#= 87iqd9rn3l.fsf@vertex.dottedmag userprop=userval
+#= 87lji4lx9v.fsf@yoom.home.cworth.org userprop=userval3
+#= 87lji5cbwo.fsf@yoom.home.cworth.org userprop=userval
+#= cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com userprop=userval
+EOF
+notmuch restore < prop-dump
+notmuch reindex '*'
+notmuch dump | grep '^#=' | sort > OUTPUT
+test_expect_equal_file prop-dump OUTPUT
+test_done
+
+add_email_corpus lkml
+
+test_begin_subtest "reindex of lkml corpus preserves threads"
+notmuch search '*' | notmuch_search_sanitize > EXPECTED
+notmuch reindex '*'
+notmuch search '*' | notmuch_search_sanitize > OUTPUT
+test_expect_equal_file EXPECTED OUTPUT
+
+test_done
int kw_val=0;
int fl_val=0;
int int_val=0;
- char *pos_arg1=NULL;
- char *pos_arg2=NULL;
- char *string_val=NULL;
-
- notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &kw_val, "keyword", 'k',
- (notmuch_keyword_t []){ { "one", 1 },
- { "two", 2 },
- { 0, 0 } } },
- { NOTMUCH_OPT_KEYWORD_FLAGS, &fl_val, "flag", 'f',
+ const char *pos_arg1=NULL;
+ const char *pos_arg2=NULL;
+ const char *string_val=NULL;
+ bool bool_val = false;
+ bool fl_set = false, int_set = false, bool_set = false,
+ kw_set = false, string_set = false, pos1_set = false, pos2_set = false;
+
+ notmuch_opt_desc_t parent_options[] = {
+ { .opt_flags = &fl_val, .name = "flag", .present = &fl_set, .keywords =
(notmuch_keyword_t []){ { "one", 1 << 0},
{ "two", 1 << 1 },
{ "three", 1 << 2 },
{ 0, 0 } } },
- { NOTMUCH_OPT_INT, &int_val, "int", 'i', 0},
- { NOTMUCH_OPT_STRING, &string_val, "string", 's', 0},
- { NOTMUCH_OPT_POSITION, &pos_arg1, 0,0, 0},
- { NOTMUCH_OPT_POSITION, &pos_arg2, 0,0, 0},
- { 0, 0, 0, 0, 0 } };
+ { .opt_int = &int_val, .name = "int", .present = &int_set },
+ { }
+ };
+
+ notmuch_opt_desc_t options[] = {
+ { .opt_bool = &bool_val, .name = "boolean", .present = &bool_set },
+ { .opt_keyword = &kw_val, .name = "keyword", .present = &kw_set, .keywords =
+ (notmuch_keyword_t []){ { "zero", 0 },
+ { "one", 1 },
+ { "two", 2 },
+ { 0, 0 } } },
+ { .opt_inherit = parent_options },
+ { .opt_string = &string_val, .name = "string", .present = &string_set },
+ { .opt_position = &pos_arg1, .present = &pos1_set },
+ { .opt_position = &pos_arg2, .present = &pos2_set },
+ { }
+ };
opt_index = parse_arguments(argc, argv, options, 1);
if (opt_index < 0)
return 1;
- if (kw_val)
+ if (bool_set)
+ printf("boolean %d\n", bool_val);
+
+ if (kw_set)
printf("keyword %d\n", kw_val);
- if (fl_val)
+ if (fl_set)
printf("flags %d\n", fl_val);
- if (int_val)
+ if (int_set)
printf("int %d\n", int_val);
- if (string_val)
+ if (string_set)
printf("string %s\n", string_val);
- if (pos_arg1)
+ if (pos1_set)
printf("positional arg 1 %s\n", pos_arg1);
- if (pos_arg2)
+ if (pos2_set)
printf("positional arg 2 %s\n", pos_arg2);
--- /dev/null
+# Source this script to set and export NOTMUCH_SRCDIR and
+# NOTMUCH_BUILDDIR.
+#
+# For this to work, always have current directory somewhere within the
+# build directory hierarchy, and run the script sourcing this script
+# using a path (relative or absolute) to the source directory.
+
+if [[ -z "${NOTMUCH_SRCDIR}" ]]; then
+ export NOTMUCH_SRCDIR="$(cd "$(dirname "$0")"/.. && pwd)"
+fi
+
+find_builddir()
+{
+ local dir="$1"
+
+ while [[ -n "$dir" ]] && [[ "$dir" != "/" ]]; do
+ if [[ -x "$dir/notmuch" ]] && [[ ! -d "$dir/notmuch" ]]; then
+ echo "$dir"
+ break
+ fi
+ dir="$(dirname "$dir")"
+ done
+}
+
+if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
+ export NOTMUCH_BUILDDIR="$(find_builddir "$(pwd)")"
+
+ if [[ -z "${NOTMUCH_BUILDDIR}" ]]; then
+ echo "Run tests in a subdir of built notmuch tree." >&2
+ exit 1
+ fi
+fi
DECODE
};
-static int inplace = FALSE;
+static bool inplace = false;
static int
xcode (void *ctx, enum direction dir, char *in, char **buf_p, size_t *size_p)
main (int argc, char **argv)
{
- enum direction dir = DECODE;
- int omit_newline = FALSE;
+ int dir = DECODE;
+ bool omit_newline = false;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_KEYWORD, &dir, "direction", 'd',
+ { .opt_keyword = &dir, .name = "direction", .keywords =
(notmuch_keyword_t []){ { "encode", ENCODE },
{ "decode", DECODE },
{ 0, 0 } } },
- { NOTMUCH_OPT_BOOLEAN, &omit_newline, "omit-newline", 'n', 0 },
- { NOTMUCH_OPT_BOOLEAN, &inplace, "in-place", 'i', 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_bool = &omit_newline, .name = "omit-newline" },
+ { .opt_bool = &inplace, .name = "in-place" },
+ { }
};
int opt_index = parse_arguments (argc, argv, options, 1);
char *buffer = NULL;
size_t buf_size = 0;
- notmuch_bool_t read_stdin = TRUE;
+ bool read_stdin = true;
for (; opt_index < argc; opt_index++) {
if (! omit_newline)
putchar ('\n');
- read_stdin = FALSE;
+ read_stdin = false;
}
if (! read_stdin)
exit 1
fi
-cd "$(dirname "$0")"
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/export-dirs.sh || exit 1
-TESTS=${NOTMUCH_TESTS:-T[0-9][0-9][0-9]-*.sh}
+TESTS=
+for test in $NOTMUCH_TESTS; do
+ TESTS="$TESTS $NOTMUCH_SRCDIR/test/$test"
+done
+
+if [[ -z "$TESTS" ]]; then
+ TESTS="$NOTMUCH_SRCDIR/test/T[0-9][0-9][0-9]-*.sh"
+fi
# Clean up any results from a previous run
-rm -rf test-results
+rm -rf $NOTMUCH_BUILDDIR/test/test-results
# Test for timeout utility
if command -v timeout >/dev/null; then
trap 'e=$?; kill $!; exit $e' HUP INT TERM
# Run the tests
for test in $TESTS; do
- $TEST_TIMEOUT_CMD ./$test "$@" &
+ $TEST_TIMEOUT_CMD $test "$@" &
wait $!
# If the test failed without producing results, then it aborted,
# so we should abort, too.
RES=$?
- if [[ $RES != 0 && ! -e "test-results/${test%.sh}" ]]; then
+ testname=$(basename $test .sh)
+ if [[ $RES != 0 && ! -e "$NOTMUCH_BUILDDIR/test/test-results/$testname" ]]; then
exit $RES
fi
done
# Report results
echo
-./aggregate-results.sh test-results/*
+$NOTMUCH_SRCDIR/test/aggregate-results.sh $NOTMUCH_BUILDDIR/test/test-results/*
ev=$?
# Clean up
-rm -rf test-results corpus.mail
+rm -rf $NOTMUCH_BUILDDIR/test/test-results $NOTMUCH_BUILDDIR/test/corpora.mail
exit $ev
/* stubs since we cannot link with notmuch.o */
const notmuch_opt_desc_t notmuch_shared_options[] = {
- { 0, 0, 0, 0, 0 }
+ { }
};
-char *notmuch_requested_db_uuid = NULL;
+const char *notmuch_requested_db_uuid = NULL;
void
notmuch_process_shared_options (unused (const char *dummy))
void *ctx = talloc_new (NULL);
- char *config_path = NULL;
+ const char *config_path = NULL;
notmuch_config_t *config;
notmuch_database_t *notmuch;
int seed = 734569;
notmuch_opt_desc_t options[] = {
- { NOTMUCH_OPT_STRING, &config_path, "config-path", 'c', 0 },
- { NOTMUCH_OPT_INT, &num_messages, "num-messages", 'n', 0 },
- { NOTMUCH_OPT_INT, &max_tags, "max-tags", 'm', 0 },
- { NOTMUCH_OPT_INT, &message_id_len, "message-id-len", 'M', 0 },
- { NOTMUCH_OPT_INT, &tag_len, "tag-len", 't', 0 },
- { NOTMUCH_OPT_INT, &seed, "seed", 's', 0 },
- { 0, 0, 0, 0, 0 }
+ { .opt_string = &config_path, .name = "config-path" },
+ { .opt_int = &num_messages, .name = "num-messages" },
+ { .opt_int = &max_tags, .name = "max-tags" },
+ { .opt_int = &message_id_len, .name = "message-id-len" },
+ { .opt_int = &tag_len, .name = "tag-len" },
+ { .opt_int = &seed, .name = "seed" },
+ { }
};
int opt_index = parse_arguments (argc, argv, options, 1);
exit (1);
}
- config = notmuch_config_open (ctx, config_path, FALSE);
+ config = notmuch_config_open (ctx, config_path, false);
if (config == NULL)
return 1;
static void
receive_data_to_file (FILE *peer, FILE *output)
{
- char *line = NULL;
- size_t line_size;
- ssize_t line_len;
+ char *line = NULL;
+ size_t line_size;
+ ssize_t line_len;
- while ((line_len = getline (&line, &line_size, peer)) != -1) {
- if (STRNCMP_LITERAL (line, ".\r\n") == 0)
- break;
- if (line_len < 2)
- continue;
- if (line[line_len-1] == '\n' && line[line_len-2] == '\r') {
- line[line_len-2] = '\n';
- line[line_len-1] = '\0';
- }
- fprintf (output, "%s",
- line[0] == '.' ? line + 1 : line);
+ while ((line_len = getline (&line, &line_size, peer)) != -1) {
+ if (STRNCMP_LITERAL (line, ".\r\n") == 0)
+ break;
+ if (line_len < 2)
+ continue;
+ if (line[line_len - 1] == '\n' && line[line_len - 2] == '\r') {
+ line[line_len - 2] = '\n';
+ line[line_len - 1] = '\0';
}
+ fprintf (output, "%s",
+ line[0] == '.' ? line + 1 : line);
+ }
- free (line);
+ free (line);
}
static int
process_command (FILE *peer, FILE *output, const char *command)
{
- if (STRNCMP_LITERAL (command, "EHLO ") == 0) {
- fprintf (peer, "502 not implemented\r\n");
- fflush (peer);
- } else if (STRNCMP_LITERAL (command, "HELO ") == 0) {
- fprintf (peer, "250 localhost\r\n");
- fflush (peer);
- } else if (STRNCMP_LITERAL (command, "MAIL FROM:") == 0 ||
- STRNCMP_LITERAL (command, "RCPT TO:") == 0) {
- fprintf (peer, "250 OK\r\n");
- fflush (peer);
- } else if (STRNCMP_LITERAL (command, "DATA") == 0) {
- fprintf (peer, "354 End data with <CR><LF>.<CR><LF>\r\n");
- fflush (peer);
- receive_data_to_file (peer, output);
- fprintf (peer, "250 OK\r\n");
- fflush (peer);
- } else if (STRNCMP_LITERAL (command, "QUIT") == 0) {
- fprintf (peer, "221 BYE\r\n");
- fflush (peer);
- return 1;
- } else {
- fprintf (stderr, "Unknown command: %s\n", command);
- }
- return 0;
+ if (STRNCMP_LITERAL (command, "EHLO ") == 0) {
+ fprintf (peer, "502 not implemented\r\n");
+ fflush (peer);
+ } else if (STRNCMP_LITERAL (command, "HELO ") == 0) {
+ fprintf (peer, "250 localhost\r\n");
+ fflush (peer);
+ } else if (STRNCMP_LITERAL (command, "MAIL FROM:") == 0 ||
+ STRNCMP_LITERAL (command, "RCPT TO:") == 0) {
+ fprintf (peer, "250 OK\r\n");
+ fflush (peer);
+ } else if (STRNCMP_LITERAL (command, "DATA") == 0) {
+ fprintf (peer, "354 End data with <CR><LF>.<CR><LF>\r\n");
+ fflush (peer);
+ receive_data_to_file (peer, output);
+ fprintf (peer, "250 OK\r\n");
+ fflush (peer);
+ } else if (STRNCMP_LITERAL (command, "QUIT") == 0) {
+ fprintf (peer, "221 BYE\r\n");
+ fflush (peer);
+ return 1;
+ } else {
+ fprintf (stderr, "Unknown command: %s\n", command);
+ }
+ return 0;
}
static void
do_smtp_to_file (FILE *peer, FILE *output)
{
- char *line = NULL;
- size_t line_size;
- ssize_t line_len;
+ char *line = NULL;
+ size_t line_size;
+ ssize_t line_len;
- fprintf (peer, "220 localhost smtp-dummy\r\n");
- fflush (peer);
+ fprintf (peer, "220 localhost smtp-dummy\r\n");
+ fflush (peer);
- while ((line_len = getline (&line, &line_size, peer)) != -1) {
- if (process_command (peer, output, line))
- break;
- }
+ while ((line_len = getline (&line, &line_size, peer)) != -1) {
+ if (process_command (peer, output, line))
+ break;
+ }
- free (line);
+ free (line);
}
int
main (int argc, char *argv[])
{
- const char * progname;
- char *output_filename;
- FILE *peer_file, *output;
- int sock, peer, err;
- struct sockaddr_in addr, peer_addr;
- struct hostent *hostinfo;
- socklen_t peer_addr_len;
- int reuse;
- int background;
+ const char *progname;
+ char *output_filename;
+ FILE *peer_file = NULL, *output = NULL;
+ int sock = -1, peer, err;
+ struct sockaddr_in addr, peer_addr;
+ struct hostent *hostinfo;
+ socklen_t peer_addr_len;
+ int reuse;
+ int background;
+ int ret = 0;
- progname = argv[0];
+ progname = argv[0];
- background = 0;
- for (; argc >= 2; argc--, argv++) {
- if (argv[1][0] != '-')
- break;
- if (strcmp (argv[1], "--") == 0) {
- argc--;
- argv++;
- break;
- }
- if (strcmp (argv[1], "--background") == 0) {
- background = 1;
- continue;
- }
- fprintf(stderr, "%s: unregognized option '%s'\n",
- progname, argv[1]);
- return 1;
+ background = 0;
+ for (; argc >= 2; argc--, argv++) {
+ if (argv[1][0] != '-')
+ break;
+ if (strcmp (argv[1], "--") == 0) {
+ argc--;
+ argv++;
+ break;
}
-
- if (argc != 2) {
- fprintf (stderr,
- "Usage: %s [--background] <output-file>\n", progname);
- return 1;
+ if (strcmp (argv[1], "--background") == 0) {
+ background = 1;
+ continue;
}
+ fprintf (stderr, "%s: unregognized option '%s'\n",
+ progname, argv[1]);
+ return 1;
+ }
- output_filename = argv[1];
- output = fopen (output_filename, "w");
- if (output == NULL) {
- fprintf (stderr, "Failed to open %s for writing: %s\n",
- output_filename, strerror (errno));
- return 1;
- }
+ if (argc != 2) {
+ fprintf (stderr,
+ "Usage: %s [--background] <output-file>\n", progname);
+ return 1;
+ }
- sock = socket (AF_INET, SOCK_STREAM, 0);
- if (sock == -1) {
- fprintf (stderr, "Error: socket() failed: %s\n",
- strerror (errno));
- return 1;
- }
+ output_filename = argv[1];
+ output = fopen (output_filename, "w");
+ if (output == NULL) {
+ fprintf (stderr, "Failed to open %s for writing: %s\n",
+ output_filename, strerror (errno));
+ ret = 1;
+ goto DONE;
+ }
- reuse = 1;
- err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
- if (err) {
- fprintf (stderr, "Error: setsockopt() failed: %s\n",
- strerror (errno));
- return 1;
- }
+ sock = socket (AF_INET, SOCK_STREAM, 0);
+ if (sock == -1) {
+ fprintf (stderr, "Error: socket() failed: %s\n",
+ strerror (errno));
+ ret = 1;
+ goto DONE;
+ }
- hostinfo = gethostbyname ("localhost");
- if (hostinfo == NULL) {
- fprintf (stderr, "Unknown host: localhost\n");
- return 1;
- }
+ reuse = 1;
+ err = setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
+ if (err) {
+ fprintf (stderr, "Error: setsockopt() failed: %s\n",
+ strerror (errno));
+ ret = 1;
+ goto DONE;
+ }
- memset (&addr, 0, sizeof (addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons (25025);
- addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
- err = bind (sock, (struct sockaddr *) &addr, sizeof(addr));
- if (err) {
- fprintf (stderr, "Error: bind() failed: %s\n",
- strerror (errno));
- close (sock);
- return 1;
- }
+ hostinfo = gethostbyname ("localhost");
+ if (hostinfo == NULL) {
+ fprintf (stderr, "Unknown host: localhost\n");
+ ret = 1;
+ goto DONE;
+ }
- err = listen (sock, 1);
- if (err) {
- fprintf (stderr, "Error: listen() failed: %s\n",
- strerror (errno));
- close (sock);
- return 1;
- }
+ memset (&addr, 0, sizeof (addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons (25025);
+ addr.sin_addr = *(struct in_addr *) hostinfo->h_addr;
+ err = bind (sock, (struct sockaddr *) &addr, sizeof (addr));
+ if (err) {
+ fprintf (stderr, "Error: bind() failed: %s\n",
+ strerror (errno));
+ close (sock);
+ ret = 1;
+ goto DONE;
+ }
- if (background) {
- int pid = fork ();
- if (pid > 0) {
- printf ("smtp_dummy_pid='%d'\n", pid);
- fflush (stdout);
- close (sock);
- return 0;
- }
- if (pid < 0) {
- fprintf (stderr, "Error: fork() failed: %s\n",
- strerror (errno));
- close (sock);
- return 1;
- }
- /* Reached if pid == 0 (the child process). */
- /* Close stdout so that the one interested in pid value will
- also get EOF. */
- close (STDOUT_FILENO);
- /* dup2() will re-reserve fd of stdout (1) (opportunistically),
- in case fd of stderr (2) is open. If that was not open we
- don't care fd of stdout (1) either. */
- dup2 (STDERR_FILENO, STDOUT_FILENO);
+ err = listen (sock, 1);
+ if (err) {
+ fprintf (stderr, "Error: listen() failed: %s\n",
+ strerror (errno));
+ close (sock);
+ ret = 1;
+ goto DONE;
+ }
- /* This process is now out of reach of shell's job control.
- To resolve the rare but possible condition where this
- "daemon" is started but never connected this process will
- (only) have 30 seconds to exist. */
- alarm (30);
+ if (background) {
+ int pid = fork ();
+ if (pid > 0) {
+ printf ("smtp_dummy_pid='%d'\n", pid);
+ fflush (stdout);
+ close (sock);
+ ret = 0;
+ goto DONE;
}
-
- peer_addr_len = sizeof (peer_addr);
- peer = accept (sock, (struct sockaddr *) &peer_addr, &peer_addr_len);
- if (peer == -1) {
- fprintf (stderr, "Error: accept() failed: %s\n",
- strerror (errno));
- return 1;
+ if (pid < 0) {
+ fprintf (stderr, "Error: fork() failed: %s\n",
+ strerror (errno));
+ close (sock);
+ ret = 1;
+ goto DONE;
}
+ /* Reached if pid == 0 (the child process). */
+ /* Close stdout so that the one interested in pid value will
+ * also get EOF. */
+ close (STDOUT_FILENO);
+ /* dup2() will re-reserve fd of stdout (1) (opportunistically),
+ * in case fd of stderr (2) is open. If that was not open we
+ * don't care fd of stdout (1) either. */
+ dup2 (STDERR_FILENO, STDOUT_FILENO);
- peer_file = fdopen (peer, "w+");
- if (peer_file == NULL) {
- fprintf (stderr, "Error: fdopen() failed: %s\n",
- strerror (errno));
- return 1;
- }
+ /* This process is now out of reach of shell's job control.
+ * To resolve the rare but possible condition where this
+ * "daemon" is started but never connected this process will
+ * (only) have 30 seconds to exist. */
+ alarm (30);
+ }
+
+ peer_addr_len = sizeof (peer_addr);
+ peer = accept (sock, (struct sockaddr *) &peer_addr, &peer_addr_len);
+ if (peer == -1) {
+ fprintf (stderr, "Error: accept() failed: %s\n",
+ strerror (errno));
+ ret = 1;
+ goto DONE;
+ }
+
+ peer_file = fdopen (peer, "w+");
+ if (peer_file == NULL) {
+ fprintf (stderr, "Error: fdopen() failed: %s\n",
+ strerror (errno));
+ ret = 1;
+ goto DONE;
+ }
- do_smtp_to_file (peer_file, output);
+ do_smtp_to_file (peer_file, output);
+ DONE:
+ if (output)
fclose (output);
+ if (peer_file)
fclose (peer_file);
+ if (sock >= 0)
close (sock);
- return 0;
+ return ret;
}
#
type die >/dev/null 2>&1 || die () { echo "$@" >&2; exit 1; }
-find_notmuch_path ()
-{
- dir="$1"
-
- while [ -n "$dir" ]; do
- bin="$dir/notmuch"
- if [ -x "$bin" ]; then
- echo "$dir"
- return
- fi
- dir="$(dirname "$dir")"
- if [ "$dir" = "/" ]; then
- break
- fi
- done
-}
+if [[ -z "$NOTMUCH_SRCDIR" ]] || [[ -z "$NOTMUCH_BUILDDIR" ]]; then
+ echo "internal: srcdir or builddir not set" >&2
+ exit 1
+fi
backup_database () {
test_name=$(basename $0 .sh)
- rm -rf notmuch-dir-backup."$test_name"
- cp -pR ${MAIL_DIR}/.notmuch notmuch-dir-backup."${test_name}"
+ rm -rf $TMP_DIRECTORY/notmuch-dir-backup."$test_name"
+ cp -pR ${MAIL_DIR}/.notmuch $TMP_DIRECTORY/notmuch-dir-backup."${test_name}"
}
restore_database () {
test_name=$(basename $0 .sh)
rm -rf ${MAIL_DIR}/.notmuch
- cp -pR notmuch-dir-backup."${test_name}" ${MAIL_DIR}/.notmuch
+ cp -pR $TMP_DIRECTORY/notmuch-dir-backup."${test_name}" ${MAIL_DIR}/.notmuch
}
# Test the binaries we have just built. The tests are kept in
# test/ subdirectory and are run in 'trash directory' subdirectory.
-TEST_DIRECTORY=$(pwd -P)
-notmuch_path=`find_notmuch_path "$TEST_DIRECTORY"`
+TEST_DIRECTORY=$NOTMUCH_BUILDDIR/test
# Prepend $TEST_DIRECTORY/../lib to LD_LIBRARY_PATH, to make tests work
# on systems where ../notmuch depends on LD_LIBRARY_PATH.
export LD_LIBRARY_PATH
# configure output
-. $notmuch_path/sh.config || exit 1
+. "$NOTMUCH_BUILDDIR/sh.config" || exit 1
# load OS specifics
-if [ -e ./test-lib-$PLATFORM.sh ]; then
- . ./test-lib-$PLATFORM.sh || exit 1
+if [[ -e "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" ]]; then
+ . "$NOTMUCH_SRCDIR/test/test-lib-$PLATFORM.sh" || exit 1
fi
+# Generate a new message in the mail directory, with a unique message
+# ID and subject. The message is not added to the index.
+#
+# After this function returns, the filename of the generated message
+# is available as $gen_msg_filename and the message ID is available as
+# $gen_msg_id .
+#
+# This function supports named parameters with the bash syntax for
+# assigning a value to an associative array ([name]=value). The
+# supported parameters are:
+#
+# [dir]=directory/of/choice
+#
+# Generate the message in directory 'directory/of/choice' within
+# the mail store. The directory will be created if necessary.
+#
+# [filename]=name
+#
+# Store the message in file 'name'. The default is to store it
+# in 'msg-<count>', where <count> is three-digit number of the
+# message.
+#
+# [body]=text
+#
+# Text to use as the body of the email message
+#
+# '[from]="Some User <user@example.com>"'
+# '[to]="Some User <user@example.com>"'
+# '[subject]="Subject of email message"'
+# '[date]="RFC 822 Date"'
+#
+# Values for email headers. If not provided, default values will
+# be generated instead.
+#
+# '[cc]="Some User <user@example.com>"'
+# [reply-to]=some-address
+# [in-reply-to]=<message-id>
+# [references]=<message-id>
+# [content-type]=content-type-specification
+# '[header]=full header line, including keyword'
+#
+# Additional values for email headers. If these are not provided
+# then the relevant headers will simply not appear in the
+# message.
+#
+# '[id]=message-id'
+#
+# Controls the message-id of the created message.
+gen_msg_cnt=0
+gen_msg_filename=""
+gen_msg_id=""
+generate_message ()
+{
+ # This is our (bash-specific) magic for doing named parameters
+ local -A template="($@)"
+ local additional_headers
+
+ gen_msg_cnt=$((gen_msg_cnt + 1))
+ if [ -z "${template[filename]}" ]; then
+ gen_msg_name="msg-$(printf "%03d" $gen_msg_cnt)"
+ else
+ gen_msg_name=${template[filename]}
+ fi
+
+ if [ -z "${template[id]}" ]; then
+ gen_msg_id="${gen_msg_name%:2,*}@notmuch-test-suite"
+ else
+ gen_msg_id="${template[id]}"
+ fi
+
+ if [ -z "${template[dir]}" ]; then
+ gen_msg_filename="${MAIL_DIR}/$gen_msg_name"
+ else
+ gen_msg_filename="${MAIL_DIR}/${template[dir]}/$gen_msg_name"
+ mkdir -p "$(dirname "$gen_msg_filename")"
+ fi
+
+ if [ -z "${template[body]}" ]; then
+ template[body]="This is just a test message (#${gen_msg_cnt})"
+ fi
+
+ if [ -z "${template[from]}" ]; then
+ template[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"
+ fi
+
+ if [ -z "${template[to]}" ]; then
+ template[to]="Notmuch Test Suite <test_suite@notmuchmail.org>"
+ fi
+
+ if [ -z "${template[subject]}" ]; then
+ if [ -n "$test_subtest_name" ]; then
+ template[subject]="$test_subtest_name"
+ else
+ template[subject]="Test message #${gen_msg_cnt}"
+ fi
+ elif [ "${template[subject]}" = "@FORCE_EMPTY" ]; then
+ template[subject]=""
+ fi
+
+ if [ -z "${template[date]}" ]; then
+ # we use decreasing timestamps here for historical reasons;
+ # the existing test suite when we converted to unique timestamps just
+ # happened to have signicantly fewer failures with that choice.
+ local date_secs=$((978709437 - gen_msg_cnt))
+ # printf %(..)T is bash 4.2+ feature. use perl fallback if needed...
+ TZ=UTC printf -v template[date] "%(%a, %d %b %Y %T %z)T" $date_secs 2>/dev/null ||
+ template[date]=`perl -le 'use POSIX "strftime";
+ @time = gmtime '"$date_secs"';
+ print strftime "%a, %d %b %Y %T +0000", @time'`
+ fi
+
+ additional_headers=""
+ if [ ! -z "${template[header]}" ]; then
+ additional_headers="${template[header]}
+${additional_headers}"
+ fi
+
+ if [ ! -z "${template[reply-to]}" ]; then
+ additional_headers="Reply-To: ${template[reply-to]}
+${additional_headers}"
+ fi
+
+ if [ ! -z "${template[in-reply-to]}" ]; then
+ additional_headers="In-Reply-To: ${template[in-reply-to]}
+${additional_headers}"
+ fi
+
+ if [ ! -z "${template[cc]}" ]; then
+ additional_headers="Cc: ${template[cc]}
+${additional_headers}"
+ fi
+
+ if [ ! -z "${template[bcc]}" ]; then
+ additional_headers="Bcc: ${template[bcc]}
+${additional_headers}"
+ fi
+
+ if [ ! -z "${template[references]}" ]; then
+ additional_headers="References: ${template[references]}
+${additional_headers}"
+ fi
+
+ if [ ! -z "${template[content-type]}" ]; then
+ additional_headers="Content-Type: ${template[content-type]}
+${additional_headers}"
+ fi
+
+ if [ ! -z "${template[content-transfer-encoding]}" ]; then
+ additional_headers="Content-Transfer-Encoding: ${template[content-transfer-encoding]}
+${additional_headers}"
+ fi
+
+ # Note that in the way we're setting it above and using it below,
+ # `additional_headers' will also serve as the header / body separator
+ # (empty line in between).
+
+ cat <<EOF >"$gen_msg_filename"
+From: ${template[from]}
+To: ${template[to]}
+Message-Id: <${gen_msg_id}>
+Subject: ${template[subject]}
+Date: ${template[date]}
+${additional_headers}
+${template[body]}
+EOF
+}
+
+# Generate a new message and add it to the database.
+#
+# All of the arguments and return values supported by generate_message
+# are also supported here, so see that function for details.
+add_message ()
+{
+ generate_message "$@" &&
+ notmuch new > /dev/null
+}
+
if test -n "$valgrind"
then
make_symlink () {
PATH=$GIT_VALGRIND/bin:$PATH
GIT_EXEC_PATH=$GIT_VALGRIND/bin
export GIT_VALGRIND
- test -n "$notmuch_path" && MANPATH="$notmuch_path/doc/_build/man"
+ test -n "$NOTMUCH_BUILDDIR" && MANPATH="$NOTMUCH_BUILDDIR/doc/_build/man"
else # normal case
- if test -n "$notmuch_path"
+ if test -n "$NOTMUCH_BUILDDIR"
then
- PATH="$notmuch_path:$PATH"
- MANPATH="$notmuch_path/doc/_build/man"
+ PATH="$NOTMUCH_BUILDDIR:$PATH"
+ MANPATH="$NOTMUCH_BUILDDIR/doc/_build/man"
fi
fi
export PATH MANPATH
# Test repository
test="tmp.$(basename "$0" .sh)"
-test -n "$root" && test="$root/$test"
-case "$test" in
-/*) TMP_DIRECTORY="$test" ;;
- *) TMP_DIRECTORY="$TEST_DIRECTORY/$test" ;;
-esac
+TMP_DIRECTORY="$TEST_DIRECTORY/$test"
test ! -z "$debug" || remove_tmp=$TMP_DIRECTORY
-rm -fr "$test" || {
+rm -rf "$TMP_DIRECTORY" || {
GIT_EXIT_OK=t
echo >&6 "FATAL: Cannot prepare test area"
exit 1
MAIL_DIR="${TMP_DIRECTORY}/mail"
export NOTMUCH_CONFIG="${TMP_DIRECTORY}/notmuch-config"
-mkdir -p "${test}"
mkdir -p "${MAIL_DIR}"
cat <<EOF >"${NOTMUCH_CONFIG}"
# Make sure echo builtin does not expand backslash-escape sequences by default.
shopt -u xpg_echo
+# Ensure NOTMUCH_SRCDIR and NOTMUCH_BUILDDIR are set.
+. $(dirname "$0")/export-dirs.sh || exit 1
+
+# It appears that people try to run tests without building...
+if [[ ! -x "$NOTMUCH_BUILDDIR/notmuch" ]]; then
+ echo >&2 'You do not seem to have built notmuch yet.'
+ exit 1
+fi
+
this_test=${0##*/}
this_test=${this_test%.sh}
this_test_bare=${this_test#T[0-9][0-9][0-9]-}
# For emacsclient
unset ALTERNATE_EDITOR
+add_gnupg_home ()
+{
+ local output
+ [ -d ${GNUPGHOME} ] && return
+ _gnupg_exit () { gpgconf --kill all 2>/dev/null || true; }
+ at_exit_function _gnupg_exit
+ mkdir -m 0700 "$GNUPGHOME"
+ gpg --no-tty --import <$NOTMUCH_SRCDIR/test/gnupg-secret-key.asc >"$GNUPGHOME"/import.log 2>&1
+ test_debug "cat $GNUPGHOME/import.log"
+ if (gpg --quick-random --version >/dev/null 2>&1) ; then
+ echo quick-random >> "$GNUPGHOME"/gpg.conf
+ elif (gpg --debug-quick-random --version >/dev/null 2>&1) ; then
+ echo debug-quick-random >> "$GNUPGHOME"/gpg.conf
+ fi
+ echo no-emit-version >> "$GNUPGHOME"/gpg.conf
+}
+
# Each test should start with something like this, after copyright notices:
#
# test_description='Description of this test...
valgrind=t; verbose=t; shift ;;
--tee)
shift ;; # was handled already
- --root=*)
- root=$(expr "z$1" : 'z[^=]*=\(.*\)')
- shift ;;
*)
echo "error: unknown test option '$1'" >&2; exit 1 ;;
esac
trap 'trap_exit' EXIT
trap 'trap_signal' HUP INT TERM
-# Generate a new message in the mail directory, with a unique message
-# ID and subject. The message is not added to the index.
-#
-# After this function returns, the filename of the generated message
-# is available as $gen_msg_filename and the message ID is available as
-# $gen_msg_id .
-#
-# This function supports named parameters with the bash syntax for
-# assigning a value to an associative array ([name]=value). The
-# supported parameters are:
-#
-# [dir]=directory/of/choice
-#
-# Generate the message in directory 'directory/of/choice' within
-# the mail store. The directory will be created if necessary.
-#
-# [filename]=name
-#
-# Store the message in file 'name'. The default is to store it
-# in 'msg-<count>', where <count> is three-digit number of the
-# message.
-#
-# [body]=text
-#
-# Text to use as the body of the email message
-#
-# '[from]="Some User <user@example.com>"'
-# '[to]="Some User <user@example.com>"'
-# '[subject]="Subject of email message"'
-# '[date]="RFC 822 Date"'
-#
-# Values for email headers. If not provided, default values will
-# be generated instead.
-#
-# '[cc]="Some User <user@example.com>"'
-# [reply-to]=some-address
-# [in-reply-to]=<message-id>
-# [references]=<message-id>
-# [content-type]=content-type-specification
-# '[header]=full header line, including keyword'
-#
-# Additional values for email headers. If these are not provided
-# then the relevant headers will simply not appear in the
-# message.
-#
-# '[id]=message-id'
-#
-# Controls the message-id of the created message.
-gen_msg_cnt=0
-gen_msg_filename=""
-gen_msg_id=""
-generate_message ()
-{
- # This is our (bash-specific) magic for doing named parameters
- local -A template="($@)"
- local additional_headers
-
- gen_msg_cnt=$((gen_msg_cnt + 1))
- if [ -z "${template[filename]}" ]; then
- gen_msg_name="msg-$(printf "%03d" $gen_msg_cnt)"
- else
- gen_msg_name=${template[filename]}
- fi
-
- if [ -z "${template[id]}" ]; then
- gen_msg_id="${gen_msg_name%:2,*}@notmuch-test-suite"
- else
- gen_msg_id="${template[id]}"
- fi
-
- if [ -z "${template[dir]}" ]; then
- gen_msg_filename="${MAIL_DIR}/$gen_msg_name"
- else
- gen_msg_filename="${MAIL_DIR}/${template[dir]}/$gen_msg_name"
- mkdir -p "$(dirname "$gen_msg_filename")"
- fi
-
- if [ -z "${template[body]}" ]; then
- template[body]="This is just a test message (#${gen_msg_cnt})"
- fi
-
- if [ -z "${template[from]}" ]; then
- template[from]="Notmuch Test Suite <test_suite@notmuchmail.org>"
- fi
-
- if [ -z "${template[to]}" ]; then
- template[to]="Notmuch Test Suite <test_suite@notmuchmail.org>"
- fi
-
- if [ -z "${template[subject]}" ]; then
- if [ -n "$test_subtest_name" ]; then
- template[subject]="$test_subtest_name"
- else
- template[subject]="Test message #${gen_msg_cnt}"
- fi
- elif [ "${template[subject]}" = "@FORCE_EMPTY" ]; then
- template[subject]=""
- fi
-
- if [ -z "${template[date]}" ]; then
- # we use decreasing timestamps here for historical reasons;
- # the existing test suite when we converted to unique timestamps just
- # happened to have signicantly fewer failures with that choice.
- local date_secs=$((978709437 - gen_msg_cnt))
- # printf %(..)T is bash 4.2+ feature. use perl fallback if needed...
- TZ=UTC printf -v template[date] "%(%a, %d %b %Y %T %z)T" $date_secs 2>/dev/null ||
- template[date]=`perl -le 'use POSIX "strftime";
- @time = gmtime '"$date_secs"';
- print strftime "%a, %d %b %Y %T +0000", @time'`
- fi
-
- additional_headers=""
- if [ ! -z "${template[header]}" ]; then
- additional_headers="${template[header]}
-${additional_headers}"
- fi
-
- if [ ! -z "${template[reply-to]}" ]; then
- additional_headers="Reply-To: ${template[reply-to]}
-${additional_headers}"
- fi
-
- if [ ! -z "${template[in-reply-to]}" ]; then
- additional_headers="In-Reply-To: ${template[in-reply-to]}
-${additional_headers}"
- fi
-
- if [ ! -z "${template[cc]}" ]; then
- additional_headers="Cc: ${template[cc]}
-${additional_headers}"
- fi
-
- if [ ! -z "${template[bcc]}" ]; then
- additional_headers="Bcc: ${template[bcc]}
-${additional_headers}"
- fi
-
- if [ ! -z "${template[references]}" ]; then
- additional_headers="References: ${template[references]}
-${additional_headers}"
- fi
-
- if [ ! -z "${template[content-type]}" ]; then
- additional_headers="Content-Type: ${template[content-type]}
-${additional_headers}"
- fi
-
- if [ ! -z "${template[content-transfer-encoding]}" ]; then
- additional_headers="Content-Transfer-Encoding: ${template[content-transfer-encoding]}
-${additional_headers}"
- fi
-
- # Note that in the way we're setting it above and using it below,
- # `additional_headers' will also serve as the header / body separator
- # (empty line in between).
-
- cat <<EOF >"$gen_msg_filename"
-From: ${template[from]}
-To: ${template[to]}
-Message-Id: <${gen_msg_id}>
-Subject: ${template[subject]}
-Date: ${template[date]}
-${additional_headers}
-${template[body]}
-EOF
-}
-
-# Generate a new message and add it to the database.
-#
-# All of the arguments and return values supported by generate_message
-# are also supported here, so see that function for details.
-add_message ()
-{
- generate_message "$@" &&
- notmuch new > /dev/null
-}
-
# Deliver a message with emacs and add it to the database
#
# Uses emacs to generate and deliver a message to the mail store.
# Accepts arbitrary extra emacs/elisp functions to modify the message
# before sending, which is useful to doing things like attaching files
# to the message and encrypting/signing.
+#
+# If any GNU-style long-arguments (like --quiet or --try-decrypt=true) are
+# at the head of the argument list, they are sent directly to "notmuch
+# new" after message delivery
emacs_fcc_message ()
{
+ local nmn_args=''
+ while [[ "$1" =~ ^-- ]]; do
+ nmn_args="$nmn_args $1"
+ shift
+ done
local subject="$1"
local body="$2"
shift 2
(insert \"${body}\")
$@
(notmuch-mua-send-and-exit))" || return 1
- notmuch new >/dev/null
+ notmuch new $nmn_args >/dev/null
}
# Add an existing, fixed corpus of email to the database.
if [ -d $TEST_DIRECTORY/corpora.mail/$corpus ]; then
cp -a $TEST_DIRECTORY/corpora.mail/$corpus ${MAIL_DIR}
else
- cp -a $TEST_DIRECTORY/corpora/$corpus ${MAIL_DIR}
+ cp -a $NOTMUCH_SRCDIR/test/corpora/$corpus ${MAIL_DIR}
notmuch new >/dev/null || die "'notmuch new' failed while adding email corpus"
mkdir -p $TEST_DIRECTORY/corpora.mail
cp -a ${MAIL_DIR} $TEST_DIRECTORY/corpora.mail/$corpus
# The test suite forces LC_ALL=C, but this causes Python 3 to
# decode stdin as ASCII. We need to read JSON in UTF-8, so
# override Python's stdio encoding defaults.
- output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -mjson.tool \
+ local script='import json, sys; json.dump(json.load(sys.stdin), sys.stdout, sort_keys=True, indent=4)'
+ output=$(echo "$1" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \
|| echo "$1")
- expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -mjson.tool \
+ expected=$(echo "$2" | PYTHONIOENCODING=utf-8 $NOTMUCH_PYTHON -c "$script" \
|| echo "$2")
shift 2
test_expect_equal "$output" "$expected" "$@"
notmuch dump --include=tags "${@}" | sed '/^#/d' | sort
}
+notmuch_drop_mail_headers ()
+{
+ $NOTMUCH_PYTHON -c '
+import email, sys
+msg = email.message_from_file(sys.stdin)
+for hdr in sys.argv[1:]: del msg[hdr]
+print(msg.as_string(False))
+' "$@"
+}
+
notmuch_search_sanitize ()
{
perl -pe 's/("?thread"?: ?)("?)................("?)/\1\2XXX\3/'
# --load Force loading of notmuch.el and test-lib.el
exec ${TEST_EMACS} --quick \
- --directory "$TEST_DIRECTORY/../emacs" --load notmuch.el \
- --directory "$TEST_DIRECTORY" --load test-lib.el \
+ --directory "$NOTMUCH_SRCDIR/emacs" --load notmuch.el \
+ --directory "$NOTMUCH_SRCDIR/test" --load test-lib.el \
"\$@"
EOF
chmod a+x "$TMP_DIRECTORY/run_emacs"
test -z "$missing_dependencies" || return
if [ -z "$EMACS_SERVER" ]; then
- emacs_tests="${this_test_bare}.el"
- if [ -f "$TEST_DIRECTORY/$emacs_tests" ]; then
+ emacs_tests="$NOTMUCH_SRCDIR/test/${this_test_bare}.el"
+ if [ -f "$emacs_tests" ]; then
load_emacs_tests="--eval '(load \"$emacs_tests\")'"
else
load_emacs_tests=
}
test_ruby() {
- MAIL_DIR=$MAIL_DIR ruby -I $TEST_DIRECTORY/../bindings/ruby> OUTPUT
+ MAIL_DIR=$MAIL_DIR ruby -I $NOTMUCH_SRCDIR/bindings/ruby> OUTPUT
}
test_C () {
exec_file="test${test_count}"
test_file="${exec_file}.c"
cat > ${test_file}
- ${TEST_CC} ${TEST_CFLAGS} -I${TEST_DIRECTORY} -I${NOTMUCH_SRCDIR}/lib -o ${exec_file} ${test_file} -L${TEST_DIRECTORY}/../lib/ -lnotmuch -ltalloc
+ ${TEST_CC} ${TEST_CFLAGS} -I${NOTMUCH_SRCDIR}/test -I${NOTMUCH_SRCDIR}/lib -o ${exec_file} ${test_file} -L${NOTMUCH_BUILDDIR}/lib/ -lnotmuch -ltalloc
echo "== stdout ==" > OUTPUT.stdout
echo "== stderr ==" > OUTPUT.stderr
./${exec_file} "$@" 1>>OUTPUT.stdout 2>>OUTPUT.stderr
}
-. ./test-lib-common.sh || exit 1
+. "$NOTMUCH_SRCDIR/test/test-lib-common.sh" || exit 1
if [ "${NOTMUCH_GMIME_MAJOR}" = 3 ]; then
test_subtest_broken_gmime_3 () {
# Use -P to resolve symlinks in our working directory so that the cwd
# in subprocesses like git equals our $PWD (for pathname comparisons).
-cd -P "$test" || error "Cannot set up test environment"
+cd -P "$TMP_DIRECTORY" || error "Cannot set up test environment"
if test "$verbose" = "t"
then
test_description='the verbosity options of the test framework itself.'
-. ./test-lib.sh || exit 1
+. $(dirname "$0")/test-lib.sh || exit 1
test_begin_subtest 'print something in test_expect_success and pass'
test_expect_success '
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)/util.c $(dir)/gmime-extra.c $(dir)/crypto.c
libnotmuch_util_modules := $(libnotmuch_util_c_srcs:.c=.o)
--- /dev/null
+/* notmuch - Not much of an email program, (just index and search)
+ *
+ * Copyright © 2012 Jameson Rollins
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see https://www.gnu.org/licenses/ .
+ *
+ * Authors: Jameson Rollins <jrollins@finestructure.net>
+ */
+
+#include "crypto.h"
+#include <strings.h>
+#define unused(x) x __attribute__ ((unused))
+
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
+#if (GMIME_MAJOR_VERSION < 3)
+/* Create or pass on a GPG context (GMime 2.6) */
+static notmuch_status_t
+get_gpg_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
+{
+ if (ctx == NULL || crypto == NULL)
+ return NOTMUCH_STATUS_NULL_POINTER;
+
+ if (crypto->gpgctx) {
+ *ctx = crypto->gpgctx;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ /* TODO: GMimePasswordRequestFunc */
+ crypto->gpgctx = g_mime_gpg_context_new (NULL, crypto->gpgpath ? crypto->gpgpath : "gpg");
+ if (! crypto->gpgctx) {
+ return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
+ }
+
+ g_mime_gpg_context_set_use_agent ((GMimeGpgContext *) crypto->gpgctx, true);
+ g_mime_gpg_context_set_always_trust ((GMimeGpgContext *) crypto->gpgctx, false);
+
+ *ctx = crypto->gpgctx;
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Create or pass on a PKCS7 context (GMime 2.6) */
+static notmuch_status_t
+get_pkcs7_context (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx)
+{
+ if (ctx == NULL || crypto == NULL)
+ return NOTMUCH_STATUS_NULL_POINTER;
+
+ if (crypto->pkcs7ctx) {
+ *ctx = crypto->pkcs7ctx;
+ return NOTMUCH_STATUS_SUCCESS;
+ }
+
+ /* TODO: GMimePasswordRequestFunc */
+ crypto->pkcs7ctx = g_mime_pkcs7_context_new (NULL);
+ if (! crypto->pkcs7ctx) {
+ return NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION;
+ }
+
+ g_mime_pkcs7_context_set_always_trust ((GMimePkcs7Context *) crypto->pkcs7ctx,
+ false);
+
+ *ctx = crypto->pkcs7ctx;
+ return NOTMUCH_STATUS_SUCCESS;
+}
+static const struct {
+ const char *protocol;
+ notmuch_status_t (*get_context) (_notmuch_crypto_t *crypto, GMimeCryptoContext **ctx);
+} protocols[] = {
+ {
+ .protocol = "application/pgp-signature",
+ .get_context = get_gpg_context,
+ },
+ {
+ .protocol = "application/pgp-encrypted",
+ .get_context = get_gpg_context,
+ },
+ {
+ .protocol = "application/pkcs7-signature",
+ .get_context = get_pkcs7_context,
+ },
+ {
+ .protocol = "application/x-pkcs7-signature",
+ .get_context = get_pkcs7_context,
+ },
+};
+
+/* for the specified protocol return the context pointer (initializing
+ * if needed) */
+notmuch_status_t
+_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
+ const char *protocol,
+ GMimeCryptoContext **ctx)
+{
+ if (! protocol)
+ return NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL;
+
+ /* As per RFC 1847 section 2.1: "the [protocol] value token is
+ * comprised of the type and sub-type tokens of the Content-Type".
+ * As per RFC 1521 section 2: "Content-Type values, subtypes, and
+ * parameter names as defined in this document are
+ * case-insensitive." Thus, we use strcasecmp for the protocol.
+ */
+ for (size_t i = 0; i < ARRAY_SIZE (protocols); i++) {
+ if (strcasecmp (protocol, protocols[i].protocol) == 0)
+ return protocols[i].get_context (crypto, ctx);
+ }
+
+ return NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL;
+}
+
+void
+_notmuch_crypto_cleanup (_notmuch_crypto_t *crypto)
+{
+ if (crypto->gpgctx) {
+ g_object_unref (crypto->gpgctx);
+ crypto->gpgctx = NULL;
+ }
+
+ if (crypto->pkcs7ctx) {
+ g_object_unref (crypto->pkcs7ctx);
+ crypto->pkcs7ctx = NULL;
+ }
+}
+#else
+void _notmuch_crypto_cleanup (unused(_notmuch_crypto_t *crypto))
+{
+}
+#endif
--- /dev/null
+#ifndef _CRYPTO_H
+#define _CRYPTO_H
+
+#include <stdbool.h>
+#if (GMIME_MAJOR_VERSION < 3)
+#include "gmime-extra.h"
+#include "notmuch.h"
+#endif
+
+typedef struct _notmuch_crypto {
+ bool verify;
+ bool decrypt;
+#if (GMIME_MAJOR_VERSION < 3)
+ GMimeCryptoContext* gpgctx;
+ GMimeCryptoContext* pkcs7ctx;
+ const char *gpgpath;
+#endif
+} _notmuch_crypto_t;
+
+
+#if (GMIME_MAJOR_VERSION < 3)
+notmuch_status_t
+_notmuch_crypto_get_gmime_ctx_for_protocol (_notmuch_crypto_t *crypto,
+ const char *protocol,
+ GMimeCryptoContext **ctx);
+#endif
+
+void
+_notmuch_crypto_cleanup (_notmuch_crypto_t *crypto);
+
+#endif
fprintf (stderr, "Internal error: ");
vfprintf (stderr, format, va_args);
+ va_end (va_args);
exit (1);
}
#define g_mime_certificate_get_fpr16(cert) g_mime_certificate_get_key_id (cert)
#define g_mime_certificate_get_uid(cert) g_mime_certificate_get_name (cert);
#else /* GMime >= 3.0 */
-typedef GMimeAddressType GMimeRecipientType;
#define GMIME_ENABLE_RFC_2047_WORKAROUNDS 0xdeadbeef
#define g_mime_certificate_get_uid(cert) g_mime_certificate_get_key_id (cert);
#define g_mime_init(flags) g_mime_init()
#define g_mime_message_add_recipient(m,t,n,a) g_mime_message_add_mailbox (m,t,n,a)
#define g_mime_message_set_subject(m,s) g_mime_message_set_subject(m,s,NULL)
-#define g_mime_multipart_encrypted_decrypt(mpe,ctx,out,err) g_mime_multipart_encrypted_decrypt(mpe, GMIME_DECRYPT_NONE, NULL, out, err)
#define g_mime_multipart_signed_verify(mps,ctx,err) g_mime_multipart_signed_verify(mps, GMIME_ENCRYPT_NONE, err)
#define g_mime_object_write_to_stream(o,s) g_mime_object_write_to_stream (o,NULL,s)
#define g_mime_object_set_header(o,h,v) g_mime_object_set_header (o,h,v,NULL)
return hash;
}
+
+void
+strip_trailing (char *str, char ch)
+{
+ int i;
+
+ for (i = strlen (str) - 1; i >= 0; i--) {
+ if (str[i] == ch)
+ str[i] = '\0';
+ else
+ break;
+ }
+}
/* GLib GHashFunc compatible case insensitive hash function */
unsigned int strcase_hash (const void *ptr);
+void strip_trailing (char *str, char ch);
+
#ifdef __cplusplus
}
#endif