From: David Bremner Date: Mon, 6 Nov 2017 00:25:54 +0000 (-0400) Subject: Merge branch 'release' X-Git-Tag: 0.26_rc0~64 X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=commitdiff_plain;h=7ac96b149f5a0e5c03b64856d7c20789dab3c628;hp=83f266136369452b859393429b8530efac2e09fb Merge branch 'release' Changes from 0.25.2 release --- diff --git a/.gitignore b/.gitignore index 7b283fb3..e06101ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,17 @@ -.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 diff --git a/.travis.yml b/.travis.yml index dbd6434e..802efd98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,19 +1,22 @@ 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: diff --git a/Makefile.local b/Makefile.local index 6bc78ef8..9505b7fe 100644 --- a/Makefile.local +++ b/Makefile.local @@ -182,14 +182,14 @@ verify-newer: # 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)) @@ -199,9 +199,17 @@ quiet ?= $($(shell echo $1 | sed -e s'/ .*//')) @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 @@ -225,6 +233,7 @@ notmuch_client_srcs = \ notmuch-dump.c \ notmuch-insert.c \ notmuch-new.c \ + notmuch-reindex.c \ notmuch-reply.c \ notmuch-restore.c \ notmuch-search.c \ @@ -237,7 +246,6 @@ notmuch_client_srcs = \ sprinter-text.c \ query-string.c \ mime-node.c \ - crypto.c \ tag-util.c notmuch_client_modules = $(notmuch_client_srcs:.c=.o) @@ -279,9 +287,20 @@ endif 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) diff --git a/NEWS b/NEWS index 24e7982c..412c678d 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,33 @@ +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) =========================== diff --git a/bindings/python/.gitignore b/bindings/python/.gitignore index da0732e9..601acdd7 100644 --- a/bindings/python/.gitignore +++ b/bindings/python/.gitignore @@ -1,4 +1,4 @@ *.py[co] /docs/build /docs/html -build/ +/build/ diff --git a/bindings/python/README b/bindings/python/README index fe7a2181..5bf076d2 100644 --- a/bindings/python/README +++ b/bindings/python/README @@ -3,7 +3,7 @@ notmuch -- The python interface to notmuch 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 diff --git a/bindings/python/docs/source/database.rst b/bindings/python/docs/source/database.rst index 5f1cdc14..079dc754 100644 --- a/bindings/python/docs/source/database.rst +++ b/bindings/python/docs/source/database.rst @@ -25,7 +25,7 @@ .. automethod:: get_directory - .. automethod:: add_message + .. automethod:: index_file .. automethod:: remove_message diff --git a/bindings/python/notmuch/database.py b/bindings/python/notmuch/database.py index 8f918069..1279804a 100644 --- a/bindings/python/notmuch/database.py +++ b/bindings/python/notmuch/database.py @@ -19,6 +19,7 @@ Copyright 2010 Sebastian Spaeth 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 ( @@ -285,7 +286,7 @@ class Database(object): """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. @@ -399,12 +400,13 @@ class Database(object): # 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 @@ -455,7 +457,7 @@ class Database(object): """ 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) @@ -467,6 +469,14 @@ class Database(object): 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 diff --git a/bindings/python/notmuch/directory.py b/bindings/python/notmuch/directory.py index 7f86b1ac..b30c9e35 100644 --- a/bindings/python/notmuch/directory.py +++ b/bindings/python/notmuch/directory.py @@ -93,7 +93,7 @@ class Directory(object): * 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 diff --git a/bindings/python/notmuch/message.py b/bindings/python/notmuch/message.py index fc177eb2..d5b98e4f 100644 --- a/bindings/python/notmuch/message.py +++ b/bindings/python/notmuch/message.py @@ -41,6 +41,7 @@ from .tag import Tags from .filenames import Filenames import email +import sys class Message(Python3StringMixIn): @@ -566,7 +567,7 @@ 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.""" @@ -587,8 +588,11 @@ class Message(Python3StringMixIn): 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 = [] diff --git a/bindings/ruby/.gitignore b/bindings/ruby/.gitignore index d682798a..c57ae63f 100644 --- a/bindings/ruby/.gitignore +++ b/bindings/ruby/.gitignore @@ -1,7 +1,7 @@ # .gitignore for bindings/ruby # Generated files -Makefile -mkmf.log -notmuch.so +/Makefile +/mkmf.log +/notmuch.so *.o diff --git a/bindings/ruby/database.c b/bindings/ruby/database.c index 12e6bab7..416eb709 100644 --- a/bindings/ruby/database.c +++ b/bindings/ruby/database.c @@ -291,7 +291,7 @@ notmuch_rb_database_add_message (VALUE self, VALUE pathv) 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); diff --git a/command-line-arguments.c b/command-line-arguments.c index dc517b06..1ff5aae5 100644 --- a/command-line-arguments.c +++ b/command-line-arguments.c @@ -6,114 +6,137 @@ /* 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; } /* @@ -138,9 +161,9 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_ 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; } @@ -163,40 +186,31 @@ parse_option (int argc, char **argv, const notmuch_opt_desc_t *options, int opt_ 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; } @@ -207,7 +221,7 @@ parse_arguments (int argc, char **argv, 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) { @@ -228,7 +242,7 @@ parse_arguments (int argc, char **argv, 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; } } } diff --git a/command-line-arguments.h b/command-line-arguments.h index 4c4d240e..76ca4dcb 100644 --- a/command-line-arguments.h +++ b/command-line-arguments.h @@ -1,18 +1,9 @@ #ifndef NOTMUCH_OPTS_H #define NOTMUCH_OPTS_H -#include "notmuch.h" +#include -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 @@ -24,22 +15,24 @@ typedef struct notmuch_keyword { 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; @@ -73,7 +66,7 @@ parse_arguments (int argc, char **argv, const notmuch_opt_desc_t *options, int o 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); diff --git a/compat/.gitignore b/compat/.gitignore index 107ba17a..7ede45e9 100644 --- a/compat/.gitignore +++ b/compat/.gitignore @@ -1 +1 @@ -zlib.pc +/zlib.pc diff --git a/completion/notmuch-completion.bash b/completion/notmuch-completion.bash index e4e4b36b..7aae4297 100644 --- a/completion/notmuch-completion.bash +++ b/completion/notmuch-completion.bash @@ -287,12 +287,16 @@ _notmuch_insert() 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 @@ -311,11 +315,20 @@ _notmuch_insert() _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}) ) ;; @@ -417,6 +430,32 @@ _notmuch_search() 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 @@ -532,7 +571,7 @@ _notmuch_tag() _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 diff --git a/configure b/configure index c5e2ffed..d3e30b53 100755 --- a/configure +++ b/configure @@ -40,10 +40,6 @@ if [ "$srcdir" != "." ]; then 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 @@ -646,6 +642,15 @@ if [ $WITH_DESKTOP = "1" ]; then 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... " @@ -1065,6 +1070,9 @@ zsh_completion_dir = ${ZSHCOMLETIONDIR:=\$(prefix)/share/zsh/functions/Completio # 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} diff --git a/contrib/go/.gitignore b/contrib/go/.gitignore index c394479f..223504b1 100644 --- a/contrib/go/.gitignore +++ b/contrib/go/.gitignore @@ -1,3 +1,3 @@ -src/github.com/ -pkg/ -bin/ +/src/github.com/ +/pkg/ +/bin/ diff --git a/contrib/go/src/notmuch/notmuch.go b/contrib/go/src/notmuch/notmuch.go index 2d684311..89093b2d 100644 --- a/contrib/go/src/notmuch/notmuch.go +++ b/contrib/go/src/notmuch/notmuch.go @@ -170,7 +170,7 @@ func (self *Database) GetVersion() uint { /* 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 { diff --git a/contrib/notmuch-mutt/.gitignore b/contrib/notmuch-mutt/.gitignore index 682a5779..116bb715 100644 --- a/contrib/notmuch-mutt/.gitignore +++ b/contrib/notmuch-mutt/.gitignore @@ -1,2 +1,2 @@ -notmuch-mutt.1 -README.html +/notmuch-mutt.1 +/README.html diff --git a/crypto.c b/crypto.c deleted file mode 100644 index cc45b885..00000000 --- a/crypto.c +++ /dev/null @@ -1,140 +0,0 @@ -/* 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 - */ - -#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 diff --git a/debian/.gitignore b/debian/.gitignore index e8c2e82a..cd0decc2 100644 --- a/debian/.gitignore +++ b/debian/.gitignore @@ -1,14 +1,14 @@ -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 diff --git a/debugger.c b/debugger.c index 0fa0fb6b..5cb38ac4 100644 --- a/debugger.c +++ b/debugger.c @@ -28,20 +28,20 @@ #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; } diff --git a/devel/check-out-of-tree-build.sh b/devel/check-out-of-tree-build.sh new file mode 100755 index 00000000..3e443ea2 --- /dev/null +++ b/devel/check-out-of-tree-build.sh @@ -0,0 +1,15 @@ +#!/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" diff --git a/devel/nmbug/doc/.gitignore b/devel/nmbug/doc/.gitignore index 4930881a..f25d695c 100644 --- a/devel/nmbug/doc/.gitignore +++ b/devel/nmbug/doc/.gitignore @@ -1,2 +1,2 @@ *.pyc -_build +/_build diff --git a/doc/.gitignore b/doc/.gitignore index 9fa35d08..bbb749fa 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,3 +1,3 @@ *.pyc -_build -config.dox +/_build +/config.dox diff --git a/doc/conf.py b/doc/conf.py index a3d82696..c7013bec 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -47,6 +47,10 @@ html_static_path = [] # 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 @@ -95,6 +99,14 @@ man_pages = [ 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), diff --git a/doc/index.rst b/doc/index.rst index 344606d9..4440d93a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,6 +18,8 @@ Contents: man5/notmuch-hooks man1/notmuch-insert man1/notmuch-new + man7/notmuch-properties + man1/notmuch-reindex man1/notmuch-reply man1/notmuch-restore man1/notmuch-search diff --git a/doc/man1/notmuch-address.rst b/doc/man1/notmuch-address.rst index cc31cc5a..dbac20f7 100644 --- a/doc/man1/notmuch-address.rst +++ b/doc/man1/notmuch-address.rst @@ -115,8 +115,16 @@ This command supports the following special exit status codes 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)** diff --git a/doc/man1/notmuch-compact.rst b/doc/man1/notmuch-compact.rst index e0109dce..25692c5b 100644 --- a/doc/man1/notmuch-compact.rst +++ b/doc/man1/notmuch-compact.rst @@ -46,7 +46,15 @@ of notmuch. 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)** diff --git a/doc/man1/notmuch-config.rst b/doc/man1/notmuch-config.rst index 6a51e64f..6961737f 100644 --- a/doc/man1/notmuch-config.rst +++ b/doc/man1/notmuch-config.rst @@ -15,7 +15,11 @@ DESCRIPTION =========== 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 @@ -134,6 +138,19 @@ The available configuration items are described below. 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.** Compile time feature . Current possibilities include @@ -142,6 +159,7 @@ The available configuration items are described below. **query.** + **[STORED IN DATABASE]** Expansion for named query called . See **notmuch-search-terms(7)** for more information about named queries. @@ -159,7 +177,15 @@ of notmuch. 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)** diff --git a/doc/man1/notmuch-count.rst b/doc/man1/notmuch-count.rst index 90d852ae..35a2e5e8 100644 --- a/doc/man1/notmuch-count.rst +++ b/doc/man1/notmuch-count.rst @@ -59,7 +59,15 @@ Supported options for **count** include 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)** diff --git a/doc/man1/notmuch-dump.rst b/doc/man1/notmuch-dump.rst index f3f2b394..7bc57d29 100644 --- a/doc/man1/notmuch-dump.rst +++ b/doc/man1/notmuch-dump.rst @@ -85,8 +85,8 @@ Supported options for **dump** include 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** @@ -110,7 +110,16 @@ Supported options for **dump** include 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)** diff --git a/doc/man1/notmuch-insert.rst b/doc/man1/notmuch-insert.rst index f79600d6..e2bf37d0 100644 --- a/doc/man1/notmuch-insert.rst +++ b/doc/man1/notmuch-insert.rst @@ -50,6 +50,20 @@ Supported options for **insert** include ``--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 =========== @@ -73,7 +87,14 @@ status of the **insert** command. 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)** diff --git a/doc/man1/notmuch-new.rst b/doc/man1/notmuch-new.rst index 6acfa112..bc26aa48 100644 --- a/doc/man1/notmuch-new.rst +++ b/doc/man1/notmuch-new.rst @@ -43,6 +43,18 @@ Supported options for **new** include ``--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 =========== @@ -54,7 +66,15 @@ This command supports the following special exit status code 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)** diff --git a/doc/man1/notmuch-reindex.rst b/doc/man1/notmuch-reindex.rst new file mode 100644 index 00000000..21f6c7a9 --- /dev/null +++ b/doc/man1/notmuch-reindex.rst @@ -0,0 +1,51 @@ +=============== +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)** diff --git a/doc/man1/notmuch-reply.rst b/doc/man1/notmuch-reply.rst index d73f8f1c..b6aec3c8 100644 --- a/doc/man1/notmuch-reply.rst +++ b/doc/man1/notmuch-reply.rst @@ -108,7 +108,15 @@ This command supports the following special exit status codes 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)** diff --git a/doc/man1/notmuch-restore.rst b/doc/man1/notmuch-restore.rst index cb68bc8a..b578af1f 100644 --- a/doc/man1/notmuch-restore.rst +++ b/doc/man1/notmuch-restore.rst @@ -62,14 +62,15 @@ Supported options for **restore** include **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 @@ -89,7 +90,16 @@ standard input. 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)** diff --git a/doc/man1/notmuch-search.rst b/doc/man1/notmuch-search.rst index 4e660a6f..67c1ce90 100644 --- a/doc/man1/notmuch-search.rst +++ b/doc/man1/notmuch-search.rst @@ -42,7 +42,9 @@ Supported options for **search** include 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 @@ -135,6 +137,19 @@ Supported options for **search** include 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 =========== @@ -149,8 +164,16 @@ This command supports the following special exit status codes 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)** diff --git a/doc/man1/notmuch-show.rst b/doc/man1/notmuch-show.rst index 9eb5198d..7ba091cf 100644 --- a/doc/man1/notmuch-show.rst +++ b/doc/man1/notmuch-show.rst @@ -176,7 +176,15 @@ This command supports the following special exit status codes 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)** diff --git a/doc/man1/notmuch-tag.rst b/doc/man1/notmuch-tag.rst index 2e7e1d32..88e454ba 100644 --- a/doc/man1/notmuch-tag.rst +++ b/doc/man1/notmuch-tag.rst @@ -101,7 +101,15 @@ of the tag **space in tags** 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)**, diff --git a/doc/man1/notmuch.rst b/doc/man1/notmuch.rst index cb350d1a..358f42e8 100644 --- a/doc/man1/notmuch.rst +++ b/doc/man1/notmuch.rst @@ -161,11 +161,22 @@ of notmuch. 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** diff --git a/doc/man5/notmuch-hooks.rst b/doc/man5/notmuch-hooks.rst index 3a72a80b..f07e4dab 100644 --- a/doc/man5/notmuch-hooks.rst +++ b/doc/man5/notmuch-hooks.rst @@ -49,7 +49,15 @@ The currently available hooks are described below. 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)** diff --git a/doc/man7/notmuch-properties.rst b/doc/man7/notmuch-properties.rst new file mode 100644 index 00000000..68121359 --- /dev/null +++ b/doc/man7/notmuch-properties.rst @@ -0,0 +1,87 @@ +================== +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)** diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst index 47cab48d..637f7777 100644 --- a/doc/man7/notmuch-search-terms.rst +++ b/doc/man7/notmuch-search-terms.rst @@ -9,6 +9,8 @@ SYNOPSIS **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*> ... @@ -157,7 +159,8 @@ below). The **property:** prefix searches for messages with a particular = 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 --------- @@ -235,7 +238,7 @@ Boolean and Probabilistic Prefixes 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. @@ -253,7 +256,7 @@ In general Xapian distinguishes between lists of terms and **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" @@ -262,7 +265,7 @@ the same phrase. - 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 :: @@ -419,7 +422,17 @@ Currently the following features require field processor support: 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)** diff --git a/emacs/.gitignore b/emacs/.gitignore index 8e15eed7..fbf8dde6 100644 --- a/emacs/.gitignore +++ b/emacs/.gitignore @@ -1,4 +1,4 @@ -.eldeps* -*.elc -notmuch-version.el -notmuch-pkg.el +/.eldeps* +/*.elc +/notmuch-version.el +/notmuch-pkg.el diff --git a/emacs/notmuch-address.el b/emacs/notmuch-address.el index f937e708..64887a43 100644 --- a/emacs/notmuch-address.el +++ b/emacs/notmuch-address.el @@ -159,7 +159,7 @@ matching `notmuch-address-completion-headers-regexp'. #'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))))) diff --git a/emacs/notmuch-lib.el b/emacs/notmuch-lib.el index 337b20ac..010be454 100644 --- a/emacs/notmuch-lib.el +++ b/emacs/notmuch-lib.el @@ -768,23 +768,23 @@ signaled error. This function does not return." (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 @@ -793,9 +793,9 @@ be a number indicating the exit status code of a process or a 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) @@ -808,12 +808,7 @@ You may need to restart Emacs or upgrade your notmuch Emacs package.")) 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) @@ -889,9 +884,13 @@ error." (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))))) @@ -910,30 +909,56 @@ invoke `set-process-sentinel' directly on the returned process, 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 @@ -945,12 +970,13 @@ status." ;; 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. @@ -964,7 +990,8 @@ status." ;; 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. diff --git a/emacs/notmuch-mua.el b/emacs/notmuch-mua.el index fd64b362..7a341ebf 100644 --- a/emacs/notmuch-mua.el +++ b/emacs/notmuch-mua.el @@ -218,7 +218,7 @@ mutiple parts get a header." 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)))) diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el index dd423765..99390277 100644 --- a/emacs/notmuch-show.el +++ b/emacs/notmuch-show.el @@ -1269,7 +1269,9 @@ matched." ;; 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. diff --git a/emacs/notmuch-tag.el b/emacs/notmuch-tag.el index 09d182df..0500927d 100644 --- a/emacs/notmuch-tag.el +++ b/emacs/notmuch-tag.el @@ -457,7 +457,7 @@ QUERY should be a string containing the search-terms. 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." diff --git a/emacs/notmuch-tree.el b/emacs/notmuch-tree.el index 022525ae..c00315e8 100644 --- a/emacs/notmuch-tree.el +++ b/emacs/notmuch-tree.el @@ -598,6 +598,8 @@ message will be \"unarchived\", i.e. the tag changes in (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) @@ -897,7 +899,9 @@ the same as for the function notmuch-tree." (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 diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 0aeff560..44402f8a 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -49,7 +49,20 @@ ;; Have fun, and let us know if you have any comment, questions, or ;; kudos: Notmuch list (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)) diff --git a/gmime-filter-reply.c b/gmime-filter-reply.c index b269db4e..a1ba4b45 100644 --- a/gmime-filter-reply.c +++ b/gmime-filter-reply.c @@ -16,6 +16,8 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ +#include + #include "gmime-filter-reply.h" /** @@ -87,8 +89,8 @@ static void 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 @@ -117,43 +119,43 @@ filter_filter (GMimeFilter *filter, char *inbuf, size_t inlen, size_t prespace, (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; } @@ -179,19 +181,19 @@ filter_reset (GMimeFilter *filter) { 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. @@ -201,7 +203,7 @@ g_mime_filter_reply_new (gboolean encode) { 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; diff --git a/lib/Makefile.local b/lib/Makefile.local index bf6e0649..8aa03891 100644 --- a/lib/Makefile.local +++ b/lib/Makefile.local @@ -19,7 +19,7 @@ LIBRARY_SUFFIX = so 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 @@ -38,10 +38,12 @@ libnotmuch_c_srcs = \ $(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 = \ @@ -50,6 +52,7 @@ 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 \ diff --git a/lib/add-message.cc b/lib/add-message.cc new file mode 100644 index 00000000..f5fac8be --- /dev/null +++ b/lib/add-message.cc @@ -0,0 +1,598 @@ +#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 (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 (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 (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); + +} diff --git a/lib/built-with.c b/lib/built-with.c index 2f1f0b5c..27384bd0 100644 --- a/lib/built-with.c +++ b/lib/built-with.c @@ -31,6 +31,6 @@ notmuch_built_with (const char *name) } else if (STRNCMP_LITERAL (name, "retry_lock") == 0) { return HAVE_XAPIAN_DB_RETRY_LOCK; } else { - return FALSE; + return false; } } diff --git a/lib/config.cc b/lib/config.cc index 0703b9bb..da71c16e 100644 --- a/lib/config.cc +++ b/lib/config.cc @@ -56,7 +56,7 @@ notmuch_database_set_config (notmuch_database_t *notmuch, 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()); } @@ -74,7 +74,7 @@ _metadata_value (notmuch_database_t *notmuch, 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()); } @@ -128,7 +128,7 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch, } 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; } @@ -145,9 +145,9 @@ notmuch_bool_t 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 * diff --git a/lib/database-private.h b/lib/database-private.h index 727b1d61..a499b259 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -179,14 +179,14 @@ operator&(notmuch_field_flag_t a, notmuch_field_flag_t b) 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 @@ -246,4 +246,10 @@ _notmuch_database_get_terms_with_prefix (void *ctx, Xapian::TermIterator &i, 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 diff --git a/lib/database.cc b/lib/database.cc index 5b13f541..02444e09 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -413,6 +413,12 @@ notmuch_status_to_string (notmuch_status_t status) return "Operation requires a database upgrade"; case NOTMUCH_STATUS_PATH_ERROR: return "Path supplied is illegal for this function"; + case NOTMUCH_STATUS_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"; @@ -463,12 +469,12 @@ find_doc_ids_for_term (notmuch_database_t *notmuch, *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; @@ -488,7 +494,7 @@ _notmuch_database_find_unique_doc_id (notmuch_database_t *notmuch, { 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; @@ -562,153 +568,12 @@ notmuch_database_find_message (notmuch_database_t *notmuch, } 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) { @@ -832,7 +697,7 @@ _notmuch_database_new_revision (notmuch_database_t *notmuch) * 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; @@ -995,12 +860,11 @@ notmuch_database_open_verbose (const char *path, } 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; @@ -1182,7 +1046,7 @@ _notmuch_database_reopen (notmuch_database_t *notmuch) 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; } @@ -1260,7 +1124,7 @@ notmuch_database_compact (const char *path, 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); @@ -1296,10 +1160,10 @@ notmuch_database_compact (const char *path, 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) { @@ -1455,7 +1319,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, 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; @@ -1489,7 +1353,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, 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. */ @@ -1730,7 +1594,7 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch) } 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; } @@ -1764,13 +1628,13 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch) } 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: @@ -2013,7 +1877,7 @@ notmuch_database_get_directory (notmuch_database_t *notmuch, } 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; @@ -2044,583 +1908,6 @@ _notmuch_database_generate_doc_id (notmuch_database_t *notmuch) 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 (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 (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 (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) @@ -2687,7 +1974,7 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, } 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; } @@ -2740,7 +2027,7 @@ notmuch_database_get_all_tags (notmuch_database_t *db) } 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; } } diff --git a/lib/directory.cc b/lib/directory.cc index 5de3319c..4fcb0177 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -103,7 +103,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch, 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; @@ -189,7 +189,7 @@ _notmuch_directory_create (notmuch_database_t *notmuch, _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; @@ -234,7 +234,7 @@ notmuch_directory_set_mtime (notmuch_directory_t *directory, _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; } @@ -301,7 +301,7 @@ notmuch_directory_delete (notmuch_directory_t *directory) _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); diff --git a/lib/filenames.c b/lib/filenames.c index 63e737dd..37d631d6 100644 --- a/lib/filenames.c +++ b/lib/filenames.c @@ -46,7 +46,7 @@ notmuch_bool_t notmuch_filenames_valid (notmuch_filenames_t *filenames) { if (filenames == NULL) - return FALSE; + return false; return (filenames->iterator != NULL); } diff --git a/lib/index.cc b/lib/index.cc index 10420d84..6e684f5f 100644 --- a/lib/index.cc +++ b/lib/index.cc @@ -184,7 +184,7 @@ filter_filter (GMimeFilter *gmime_filter, char *inbuf, size_t inlen, size_t pres 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; @@ -261,7 +261,7 @@ notmuch_filter_discard_non_term_new (GMimeContentType *content_type) 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")) { @@ -351,9 +351,28 @@ _index_address_list (notmuch_message_t *message, } } +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; @@ -361,6 +380,7 @@ _index_mime_part (notmuch_message_t *message, GMimeDataWrapper *wrapper; GByteArray *byte_array; GMimeContentDisposition *disposition; + GMimeContentType *content_type; char *body; const char *charset; @@ -370,15 +390,8 @@ _index_mime_part (notmuch_message_t *message, 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); @@ -392,18 +405,32 @@ _index_mime_part (notmuch_message_t *message, 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; @@ -414,7 +441,7 @@ _index_mime_part (notmuch_message_t *message, 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; } @@ -444,9 +471,10 @@ _index_mime_part (notmuch_message_t *message, 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), @@ -475,7 +503,7 @@ _index_mime_part (notmuch_message_t *message, 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); @@ -484,8 +512,71 @@ _index_mime_part (notmuch_message_t *message, } } +/* 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; @@ -513,7 +604,7 @@ _notmuch_message_index_file (notmuch_message_t *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; } diff --git a/lib/indexopts.c b/lib/indexopts.c new file mode 100644 index 00000000..15c31d24 --- /dev/null +++ b/lib/indexopts.c @@ -0,0 +1,67 @@ +/* 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 + */ + +#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); +} diff --git a/lib/message-file.c b/lib/message-file.c index d7acf0d5..8f0dbbda 100644 --- a/lib/message-file.c +++ b/lib/message-file.c @@ -92,22 +92,28 @@ _notmuch_message_file_open (notmuch_database_t *notmuch, 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); @@ -121,7 +127,7 @@ _notmuch_message_file_parse (notmuch_message_file_t *message) 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; @@ -141,7 +147,7 @@ _notmuch_message_file_parse (notmuch_message_file_t *message) 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); @@ -345,3 +351,83 @@ _notmuch_message_file_get_header (notmuch_message_file_t *message, 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; +} diff --git a/lib/message-id.c b/lib/message-id.c new file mode 100644 index 00000000..d7541d50 --- /dev/null +++ b/lib/message-id.c @@ -0,0 +1,96 @@ +#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; +} diff --git a/lib/message-private.h b/lib/message-private.h index 74199256..73b080e4 100644 --- a/lib/message-private.h +++ b/lib/message-private.h @@ -4,7 +4,7 @@ notmuch_string_map_t * _notmuch_message_property_map (notmuch_message_t *message); -notmuch_bool_t +bool _notmuch_message_frozen (notmuch_message_t *message); void diff --git a/lib/message-property.cc b/lib/message-property.cc index f32d5550..35eaf3c6 100644 --- a/lib/message-property.cc +++ b/lib/message-property.cc @@ -38,7 +38,7 @@ notmuch_message_get_property (notmuch_message_t *message, const char *key, const 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; @@ -76,17 +76,18 @@ _notmuch_message_modify_property (notmuch_message_t *message, const char *key, c 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; @@ -97,7 +98,8 @@ notmuch_message_remove_all_properties (notmuch_message_t *message, const char *k _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"); @@ -107,6 +109,18 @@ notmuch_message_remove_all_properties (notmuch_message_t *message, const char *k 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) { diff --git a/lib/message.cc b/lib/message.cc index f78e5a9d..12743460 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -36,6 +36,7 @@ struct _notmuch_message { 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; @@ -47,7 +48,7 @@ struct _notmuch_message { 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; @@ -61,16 +62,16 @@ struct _notmuch_message { 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 @@ -123,6 +124,7 @@ _notmuch_message_create_for_document (const void *talloc_owner, 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; @@ -268,7 +270,7 @@ _notmuch_message_create_for_message_id (notmuch_database_t *notmuch, } 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; } @@ -525,7 +527,7 @@ notmuch_message_get_header (notmuch_message_t *message, const char *header) } 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; } } @@ -579,7 +581,9 @@ void _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 (); @@ -592,13 +596,66 @@ _notmuch_message_remove_terms (notmuch_message_t *message, const char *prefix) 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) { @@ -646,6 +703,7 @@ _notmuch_message_add_folder_terms (notmuch_message_t *message, talloc_free (folder); + message->modified = true; return NOTMUCH_STATUS_SUCCESS; } @@ -847,7 +905,7 @@ void _notmuch_message_clear_data (notmuch_message_t *message) { message->doc.set_data (""); - message->modified = TRUE; + message->modified = true; } static void @@ -946,6 +1004,14 @@ notmuch_message_get_filenames (notmuch_message_t *message) 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) @@ -978,7 +1044,7 @@ notmuch_message_get_date (notmuch_message_t *message) } 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; } @@ -1049,7 +1115,7 @@ _notmuch_message_set_header_values (notmuch_message_t *message, 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 @@ -1059,7 +1125,7 @@ _notmuch_message_upgrade_last_mod (notmuch_message_t *message) { /* _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. */ @@ -1088,7 +1154,7 @@ _notmuch_message_sync (notmuch_message_t *message) db = static_cast (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 @@ -1104,7 +1170,7 @@ _notmuch_message_delete (notmuch_message_t *message) 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); @@ -1233,7 +1299,7 @@ _notmuch_message_add_term (notmuch_message_t *message, return NOTMUCH_PRIVATE_STATUS_TERM_TOO_LONG; message->doc.add_term (term, 0); - message->modified = TRUE; + message->modified = true; talloc_free (term); @@ -1302,7 +1368,7 @@ _notmuch_message_remove_term (notmuch_message_t *message, 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 @@ -1321,10 +1387,10 @@ notmuch_private_status_t _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) @@ -1342,7 +1408,7 @@ _notmuch_message_has_term (notmuch_message_t *message, 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; } @@ -1449,17 +1515,22 @@ _filename_is_in_maildir (const char *filename) 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)) @@ -1485,11 +1556,28 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) 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); @@ -1497,7 +1585,7 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *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) { @@ -1510,8 +1598,6 @@ notmuch_message_maildir_flags_to_tags (notmuch_message_t *message) } status = notmuch_message_thaw (message); - talloc_free (combined_flags); - return status; } @@ -1599,7 +1685,7 @@ _new_maildir_filename (void *ctx, 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; @@ -1640,7 +1726,7 @@ _new_maildir_filename (void *ctx, if (flag_map[flag] == 0) { flag_map[flag] = 1; flags_in_map++; - flags_changed = TRUE; + flags_changed = true; } } @@ -1649,7 +1735,7 @@ _new_maildir_filename (void *ctx, if (flag_map[flag]) { flag_map[flag] = 0; flags_in_map--; - flags_changed = TRUE; + flags_changed = true; } } @@ -1867,8 +1953,118 @@ _notmuch_message_property_map (notmuch_message_t *message) 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; +} diff --git a/lib/messages.c b/lib/messages.c index b5363bb8..a88f974f 100644 --- a/lib/messages.c +++ b/lib/messages.c @@ -68,7 +68,7 @@ _notmuch_messages_create (notmuch_message_list_t *list) if (unlikely (messages == NULL)) return NULL; - messages->is_of_list_type = TRUE; + messages->is_of_list_type = true; messages->iterator = list->head; return messages; @@ -93,7 +93,7 @@ notmuch_bool_t 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); diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h index 5dfebf5d..1093429c 100644 --- a/lib/notmuch-private.h +++ b/lib/notmuch-private.h @@ -24,6 +24,7 @@ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* For getline and asprintf */ #endif +#include #include #include "compat.h" @@ -51,6 +52,7 @@ NOTMUCH_BEGIN_DECLS #include "xutil.h" #include "error_util.h" #include "string-util.h" +#include "crypto.h" #ifdef DEBUG # define DEBUG_DATABASE_SANITY 1 @@ -282,7 +284,7 @@ notmuch_private_status_t _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, @@ -425,10 +427,28 @@ const char * _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 */ @@ -448,7 +468,7 @@ typedef struct _notmuch_message_list { * 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; }; @@ -465,7 +485,7 @@ _notmuch_messages_create (notmuch_message_list_t *list); /* query.cc */ -notmuch_bool_t +bool _notmuch_mset_messages_valid (notmuch_messages_t *messages); notmuch_message_t * @@ -474,7 +494,7 @@ _notmuch_mset_messages_get (notmuch_messages_t *messages); 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); @@ -492,6 +512,20 @@ notmuch_status_t _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 */ @@ -501,6 +535,8 @@ _notmuch_message_add_reply (notmuch_message_t *message, notmuch_database_t * _notmuch_message_database (notmuch_message_t *message); +void +_notmuch_message_remove_unprefixed_terms (notmuch_message_t *message); /* sha1.c */ char * @@ -525,6 +561,12 @@ typedef struct _notmuch_string_list { 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'. @@ -552,9 +594,9 @@ _notmuch_string_map_get (notmuch_string_map_t *map, const char *key); 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 @@ -593,6 +635,12 @@ _notmuch_thread_create (void *ctx, notmuch_exclude_t omit_exclude, notmuch_sort_t sort); +/* indexopts.c */ + +struct _notmuch_indexopts { + _notmuch_crypto_t crypto; +}; + NOTMUCH_END_DECLS #ifdef __cplusplus diff --git a/lib/notmuch.h b/lib/notmuch.h index 17f0872e..2c5dcab5 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -191,6 +191,23 @@ typedef enum _notmuch_status { * 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. @@ -219,6 +236,7 @@ typedef struct _notmuch_tags notmuch_tags_t; 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__ */ /** @@ -236,7 +254,7 @@ typedef struct _notmuch_config_list notmuch_config_list_t; * 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). @@ -541,8 +559,10 @@ notmuch_database_get_directory (notmuch_database_t *database, 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 @@ -555,8 +575,14 @@ notmuch_database_get_directory (notmuch_database_t *database, * 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' @@ -589,8 +615,25 @@ notmuch_database_get_directory (notmuch_database_t *database, * * 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); @@ -1096,6 +1139,18 @@ notmuch_thread_get_thread_id (notmuch_thread_t *thread); 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. @@ -1341,6 +1396,14 @@ notmuch_message_get_thread_id (notmuch_message_t *message); 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'. * @@ -1373,6 +1436,20 @@ notmuch_message_get_filename (notmuch_message_t *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. */ @@ -1546,13 +1623,21 @@ notmuch_message_remove_all_tags (notmuch_message_t *message); * * 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. * @@ -1679,6 +1764,9 @@ notmuch_message_destroy (notmuch_message_t *message); * 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. + * */ /**@{*/ /** @@ -1738,6 +1826,22 @@ notmuch_message_remove_property (notmuch_message_t *message, const char *key, co 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 */ @@ -1788,7 +1892,7 @@ notmuch_message_get_properties (notmuch_message_t *message, const char *key, not * 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. * @@ -1908,7 +2012,7 @@ notmuch_tags_destroy (notmuch_tags_t *tags); * * 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. @@ -2113,6 +2217,54 @@ notmuch_config_list_move_to_next (notmuch_config_list_t *config_list); 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 * diff --git a/lib/query.cc b/lib/query.cc index 9c6ecc8d..d633fa3d 100644 --- a/lib/query.cc +++ b/lib/query.cc @@ -29,7 +29,7 @@ struct _notmuch_query { 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 terms; }; @@ -62,12 +62,12 @@ struct _notmuch_threads { }; /* 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"); @@ -97,7 +97,7 @@ notmuch_query_create (notmuch_database_t *notmuch, new (&query->xapian_query) Xapian::Query (); new (&query->terms) std::set (); - query->parsed = FALSE; + query->parsed = false; talloc_set_destructor (query, _notmuch_query_destructor); @@ -134,7 +134,7 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query) 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) { @@ -144,7 +144,7 @@ _notmuch_query_ensure_parsed (notmuch_query_t *query) _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; @@ -261,7 +261,7 @@ _notmuch_query_search_documents (notmuch_query_t *query, 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 (); @@ -304,7 +304,7 @@ _notmuch_query_search_documents (notmuch_query_t *query, 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; @@ -322,13 +322,13 @@ _notmuch_query_search_documents (notmuch_query_t *query, 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; @@ -359,13 +359,13 @@ _notmuch_query_search_documents (notmuch_query_t *query, "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; @@ -415,7 +415,7 @@ _notmuch_mset_messages_get (notmuch_messages_t *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; } @@ -430,7 +430,7 @@ _notmuch_mset_messages_move_to_next (notmuch_messages_t *messages) 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) @@ -443,7 +443,7 @@ _notmuch_doc_id_set_init (void *ctx, 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; @@ -453,15 +453,15 @@ _notmuch_doc_id_set_init (void *ctx, 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)); } @@ -514,7 +514,7 @@ notmuch_query_search_threads (notmuch_query_t *query, 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); @@ -546,7 +546,7 @@ notmuch_threads_valid (notmuch_threads_t *threads) 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, diff --git a/lib/string-list.c b/lib/string-list.c index 43ebe499..9c3ae7ef 100644 --- a/lib/string-list.c +++ b/lib/string-list.c @@ -42,6 +42,12 @@ _notmuch_string_list_create (const void *ctx) 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) diff --git a/lib/string-map.c b/lib/string-map.c index 0bb77e93..5aac8bcc 100644 --- a/lib/string-map.c +++ b/lib/string-map.c @@ -33,14 +33,14 @@ typedef struct _notmuch_string_pair_t { } 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; }; @@ -55,7 +55,7 @@ _notmuch_string_map_create (const void *ctx) map->length = 0; map->pairs = NULL; - map->sorted = TRUE; + map->sorted = true; return map; } @@ -67,7 +67,7 @@ _notmuch_string_map_append (notmuch_string_map_t *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); @@ -103,11 +103,11 @@ _notmuch_string_map_sort (notmuch_string_map_t *map) 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)); @@ -116,7 +116,7 @@ string_cmp (const char *a, const char *b, notmuch_bool_t exact) } 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; @@ -151,7 +151,7 @@ _notmuch_string_map_get (notmuch_string_map_t *map, const char *key) /* 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; @@ -160,7 +160,7 @@ _notmuch_string_map_get (notmuch_string_map_t *map, const char *key) 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; @@ -179,15 +179,15 @@ _notmuch_string_map_iterator_create (notmuch_string_map_t *map, const char *key, 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)); diff --git a/lib/thread.cc b/lib/thread.cc index 1a1ecfa5..1632da4c 100644 --- a/lib/thread.cc +++ b/lib/thread.cc @@ -44,6 +44,7 @@ struct _notmuch_thread { GHashTable *message_hash; int total_messages; + int total_files; int matched_messages; time_t oldest; time_t newest; @@ -58,12 +59,12 @@ _notmuch_thread_destructor (notmuch_thread_t *thread) 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; } @@ -155,9 +156,9 @@ _resolve_thread_authors_string (notmuch_thread_t *thread) 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; } @@ -238,7 +239,7 @@ _thread_add_message (notmuch_thread_t *thread, 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); @@ -253,7 +254,7 @@ _thread_add_message (notmuch_thread_t *thread, { /* 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; } } @@ -266,6 +267,7 @@ _thread_add_message (notmuch_thread_t *thread, _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)), @@ -308,7 +310,7 @@ _thread_add_message (notmuch_thread_t *thread, /* 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 @@ -495,6 +497,7 @@ _notmuch_thread_create (void *ctx, free, NULL); thread->total_messages = 0; + thread->total_files = 0; thread->matched_messages = 0; thread->oldest = 0; thread->newest = 0; @@ -566,6 +569,12 @@ notmuch_thread_get_total_messages (notmuch_thread_t *thread) 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) { diff --git a/mime-node.c b/mime-node.c index 16091735..e1aca969 100644 --- a/mime-node.c +++ b/mime-node.c @@ -33,7 +33,7 @@ typedef struct mime_node_context { GMimeMessage *mime_message; /* Context provided by the caller. */ - notmuch_crypto_t *crypto; + _notmuch_crypto_t *crypto; } mime_node_context_t; static int @@ -56,7 +56,7 @@ _mime_node_context_free (mime_node_context_t *res) 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; @@ -79,12 +79,32 @@ mime_node_open (const void *ctx, notmuch_message_t *message, } 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) { @@ -92,7 +112,7 @@ mime_node_open (const void *ctx, notmuch_message_t *message, 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) { @@ -151,11 +171,11 @@ set_signature_list_destructor (mime_node_t *node) /* 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); @@ -172,23 +192,27 @@ node_verify (mime_node_t *node, GMimeObject *part, /* 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); @@ -207,7 +231,7 @@ static mime_node_t * _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; @@ -245,7 +269,12 @@ _mime_node_create (mime_node_t *parent, GMimeObject *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; } diff --git a/notmuch-client.h b/notmuch-client.h index ae37360b..f7524e59 100644 --- a/notmuch-client.h +++ b/notmuch-client.h @@ -24,6 +24,7 @@ #ifndef _GNU_SOURCE #define _GNU_SOURCE /* for getline */ #endif +#include #include #include @@ -31,10 +32,6 @@ #include "gmime-extra.h" -typedef GMimeCryptoContext notmuch_crypto_context_t; -/* This is automatically included only since gmime 2.6.10 */ -#include - #include "notmuch.h" /* This is separate from notmuch-private.h because we're trying to @@ -54,6 +51,7 @@ typedef GMimeCryptoContext notmuch_crypto_context_t; #include #include "talloc-extra.h" +#include "crypto.h" #define unused(x) x __attribute__ ((unused)) @@ -71,23 +69,13 @@ typedef struct notmuch_show_format { 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; @@ -180,14 +168,6 @@ typedef struct _notmuch_config notmuch_config_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[]); @@ -200,6 +180,9 @@ notmuch_new_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[]); @@ -244,12 +227,12 @@ show_one_part (const char *filename, int part); 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, @@ -283,7 +266,7 @@ notmuch_config_close (notmuch_config_t *config); int notmuch_config_save (notmuch_config_t *config); -notmuch_bool_t +bool notmuch_config_is_new (notmuch_config_t *config); const char * @@ -342,12 +325,12 @@ notmuch_config_set_new_ignore (notmuch_config_t *config, 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); @@ -360,7 +343,7 @@ notmuch_config_set_search_exclude_tags (notmuch_config_t *config, int notmuch_run_hook (const char *db_path, const char *hook); -notmuch_bool_t +bool debugger_is_active (void); /* mime-node.c */ @@ -403,14 +386,14 @@ struct mime_node { 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. */ @@ -445,7 +428,7 @@ struct mime_node { */ 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 @@ -484,7 +467,7 @@ notmuch_database_dump (notmuch_database_t *notmuch, 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. @@ -505,11 +488,25 @@ status_to_exit (notmuch_status_t status); #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 diff --git a/notmuch-compact.c b/notmuch-compact.c index 855545d7..f8996cf4 100644 --- a/notmuch-compact.c +++ b/notmuch-compact.c @@ -32,14 +32,14 @@ notmuch_compact_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-config.c b/notmuch-config.c index cb9529b9..1cba2661 100644 --- a/notmuch-config.c +++ b/notmuch-config.c @@ -122,7 +122,7 @@ static const char crypto_config_comment[] = struct _notmuch_config { char *filename; GKeyFile *key_file; - notmuch_bool_t is_new; + bool is_new; char *database_path; char *crypto_gpg_path; @@ -134,7 +134,7 @@ struct _notmuch_config { 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; }; @@ -212,8 +212,8 @@ get_username_from_passwd_file (void *ctx) 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; @@ -221,7 +221,7 @@ get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new) 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) { @@ -230,8 +230,8 @@ get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new) * 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", @@ -271,7 +271,7 @@ get_config_from_file (notmuch_config_t *config, notmuch_bool_t create_new) 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; } @@ -352,7 +352,7 @@ notmuch_config_open (void *ctx, 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); @@ -366,7 +366,7 @@ notmuch_config_open (void *ctx, 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); @@ -466,7 +466,7 @@ notmuch_config_open (void *ctx, 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); } @@ -579,7 +579,7 @@ notmuch_config_save (notmuch_config_t *config) return 0; } -notmuch_bool_t +bool notmuch_config_is_new (notmuch_config_t *config) { return config->is_new; @@ -808,7 +808,20 @@ _item_split (char *item, char **group, char **key) } #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) @@ -857,7 +870,7 @@ notmuch_config_command_get (notmuch_config_t *config, char *item) } 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; @@ -928,7 +941,7 @@ notmuch_config_command_set (notmuch_config_t *config, char *item, int argc, char return 1; } - if (STRNCMP_LITERAL (item, QUERY_PREFIX) == 0) { + if (_stored_in_db (item)) { return _set_db_config (config, item, argc, argv); } @@ -1086,7 +1099,7 @@ notmuch_config_command (notmuch_config_t *config, int argc, char *argv[]) } -notmuch_bool_t +bool notmuch_config_get_maildir_synchronize_flags (notmuch_config_t *config) { return config->maildir_synchronize_flags; @@ -1094,7 +1107,7 @@ 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) { g_key_file_set_boolean (config->key_file, "maildir", "synchronize_flags", synchronize_flags); diff --git a/notmuch-count.c b/notmuch-count.c index 50b0c193..ca05c979 100644 --- a/notmuch-count.c +++ b/notmuch-count.c @@ -27,12 +27,6 @@ enum { 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) @@ -160,30 +154,27 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]) 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); @@ -193,7 +184,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]) 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", @@ -204,6 +195,8 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]) if (batch && opt_index != argc) { fprintf (stderr, "--batch and query string are not compatible\n"); + if (input) + fclose (input); return EXIT_FAILURE; } @@ -219,7 +212,7 @@ notmuch_count_command (notmuch_config_t *config, int argc, char *argv[]) return EXIT_FAILURE; } - if (exclude == EXCLUDE_TRUE) { + if (exclude) { search_exclude_tags = notmuch_config_get_search_exclude_tags (config, &search_exclude_tags_length); } diff --git a/notmuch-dump.c b/notmuch-dump.c index 5cc3b2f6..ef2f02df 100644 --- a/notmuch-dump.c +++ b/notmuch-dump.c @@ -97,7 +97,7 @@ dump_properties_message (void *ctx, { 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); @@ -106,7 +106,7 @@ dump_properties_message (void *ctx, 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; @@ -116,7 +116,7 @@ dump_properties_message (void *ctx, return 1; } gzprintf (output, "#= %s", *buffer_p); - first = FALSE; + first = false; } key = notmuch_message_properties_key (list); @@ -277,7 +277,7 @@ notmuch_database_dump (notmuch_database_t *notmuch, 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"; @@ -369,26 +369,26 @@ notmuch_dump_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-insert.c b/notmuch-insert.c index bc96af0e..4d6b0a7f 100644 --- a/notmuch-insert.c +++ b/notmuch-insert.c @@ -27,6 +27,7 @@ #include #include #include +#include "string-util.h" static volatile sig_atomic_t interrupted; @@ -64,7 +65,7 @@ safe_gethostname (char *hostname, size_t len) } /* Call fsync() on a directory path. */ -static notmuch_bool_t +static bool sync_dir (const char *dir) { int fd, r; @@ -72,7 +73,7 @@ sync_dir (const char *dir) 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); @@ -87,29 +88,29 @@ sync_dir (const char *dir) /* * 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; @@ -122,13 +123,13 @@ mkdir_recursive (const void *ctx, const char *path, int mode) 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 */ @@ -137,27 +138,27 @@ mkdir_recursive (const void *ctx, const char *path, int mode) 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" }; @@ -169,14 +170,14 @@ maildir_create_folder (const void *ctx, const char *maildir) 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; } /* @@ -240,13 +241,13 @@ maildir_mktemp (const void *ctx, const char *maildir, char **path_out) } /* - * 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; @@ -261,7 +262,7 @@ copy_fd (int fdout, int fdin) continue; fprintf (stderr, "Error: reading from standard input: %s\n", strerror (errno)); - return FALSE; + return false; } p = buf; @@ -272,11 +273,11 @@ copy_fd (int fdout, int fdin) 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); } @@ -366,24 +367,25 @@ FAIL: /* * 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) { @@ -451,23 +453,24 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]) 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); @@ -488,14 +491,14 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]) 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; } @@ -508,22 +511,21 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]) 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)); @@ -545,9 +547,15 @@ notmuch_insert_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-new.c b/notmuch-new.c index 3a60f7ca..fb021b18 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -44,7 +44,7 @@ enum verbosity { 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; @@ -60,7 +60,7 @@ typedef struct { _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; @@ -234,18 +234,24 @@ _entries_resemble_maildir (const char *path, struct dirent **entries, int count) 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. */ @@ -261,16 +267,22 @@ add_file (notmuch_database_t *notmuch, const char *filename, 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). */ @@ -291,8 +303,7 @@ add_file (notmuch_database_t *notmuch, const char *filename, 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); @@ -365,7 +376,7 @@ add_files (notmuch_database_t *notmuch, 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", @@ -438,12 +449,13 @@ add_files (notmuch_database_t *notmuch, /* 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 @@ -469,13 +481,10 @@ add_files (notmuch_database_t *notmuch, 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; @@ -509,13 +518,13 @@ add_files (notmuch_database_t *notmuch, } /* 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) @@ -740,8 +749,7 @@ count_files (const char *path, int *count, add_files_state_t *state) /* 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; @@ -829,7 +837,7 @@ remove_filename (notmuch_database_t *notmuch, 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) { @@ -933,7 +941,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) 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; @@ -945,18 +953,19 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) _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); @@ -979,7 +988,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) 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); @@ -1046,7 +1055,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) } 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; } @@ -1072,6 +1081,13 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) 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. */ @@ -1093,7 +1109,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-reindex.c b/notmuch-reindex.c new file mode 100644 index 00000000..5d702510 --- /dev/null +++ b/notmuch-reindex.c @@ -0,0 +1,142 @@ +/* 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 + */ + +#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; +} diff --git a/notmuch-reply.c b/notmuch-reply.c index 40201b80..2c7cc4eb 100644 --- a/notmuch-reply.c +++ b/notmuch-reply.c @@ -93,7 +93,7 @@ typedef enum { } 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) { @@ -105,7 +105,7 @@ match_address (const char *str, const char *address, address_match_t mode) return strcasecmp (address, str) == 0; } - return FALSE; + return false; } /* Match given string against user's configured "primary" and "other" @@ -153,7 +153,7 @@ string_in_user_address (const char *str, notmuch_config_t *config) /* 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; @@ -221,7 +221,7 @@ scan_address_list (InternetAddressList *list, /* 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) { @@ -229,7 +229,7 @@ reply_to_header_is_redundant (GMimeMessage *message, InternetAddress *address; InternetAddressMailbox *mailbox; InternetAddressList *recipients; - notmuch_bool_t ret = FALSE; + bool ret = false; int i; if (reply_to_list == NULL || @@ -253,7 +253,7 @@ reply_to_header_is_redundant (GMimeMessage *message, mailbox = INTERNET_ADDRESS_MAILBOX (address); addr = internet_address_mailbox_get_addr (mailbox); if (strcmp (addr, reply_to) == 0) { - ret = TRUE; + ret = true; break; } } @@ -323,7 +323,7 @@ static const char * 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 @@ -522,8 +522,8 @@ create_reply_message(void *ctx, 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; @@ -612,7 +612,7 @@ static int do_reply(notmuch_config_t *config, 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; @@ -663,11 +663,11 @@ static int do_reply(notmuch_config_t *config, /* 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); @@ -702,23 +702,23 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]) .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); @@ -759,7 +759,7 @@ notmuch_reply_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-restore.c b/notmuch-restore.c index d6429efb..dee19c20 100644 --- a/notmuch-restore.c +++ b/notmuch-restore.c @@ -211,7 +211,7 @@ parse_sup_line (void *ctx, char *line, tok_len++; } - if (tag_op_list_append (tag_ops, tok, FALSE)) + if (tag_op_list_append (tag_ops, tok, false)) return -1; } @@ -223,11 +223,11 @@ int 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; @@ -247,20 +247,20 @@ notmuch_restore_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-search.c b/notmuch-search.c index 019e14ee..0abac08e 100644 --- a/notmuch-search.c +++ b/notmuch-search.c @@ -51,17 +51,17 @@ typedef enum { 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 { @@ -160,9 +160,10 @@ do_search_threads (search_context_t *ctx) 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); @@ -175,13 +176,23 @@ do_search_threads (search_context_t *ctx) 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); @@ -232,7 +243,7 @@ do_search_threads (search_context_t *ctx) 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); @@ -284,9 +295,9 @@ static int mailbox_compare (const void *v1, const void *v2) 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; @@ -304,12 +315,12 @@ is_duplicate (const search_context_t *ctx, const char *name, const char *addr) 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 @@ -320,24 +331,24 @@ is_duplicate (const search_context_t *ctx, const char *name, const char *addr) 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 @@ -352,7 +363,7 @@ print_mailbox (const search_context_t *ctx, const mailbox_t *mailbox) /* name_addr has the name part quoted if necessary. Compare * 'John Doe ' vs. '"Doe, John" ' */ - 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) { @@ -775,18 +786,18 @@ static search_context_t search_context = { }; 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 @@ -796,25 +807,25 @@ notmuch_search_command (notmuch_config_t *config, int argc, char *argv[]) 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; @@ -862,23 +873,23 @@ notmuch_address_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-setup.c b/notmuch-setup.c index 9a66810d..53048005 100644 --- a/notmuch-setup.c +++ b/notmuch-setup.c @@ -187,7 +187,7 @@ notmuch_setup_command (notmuch_config_t *config, (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)); @@ -210,7 +210,7 @@ notmuch_setup_command (notmuch_config_t *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); } @@ -227,7 +227,7 @@ notmuch_setup_command (notmuch_config_t *config, (const char **) tags->pdata, tags->len); - g_ptr_array_free (tags, TRUE); + g_ptr_array_free (tags, true); } if (notmuch_config_save (config)) diff --git a/notmuch-show.c b/notmuch-show.c index 74e77249..7afd3947 100644 --- a/notmuch-show.c +++ b/notmuch-show.c @@ -196,7 +196,7 @@ _is_from_line (const char *line) 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. */ @@ -283,7 +283,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, 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); @@ -305,7 +305,7 @@ show_text_part_content (GMimeObject *part, GMimeStream *stream_out, 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); @@ -350,7 +350,7 @@ do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map, 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); } } @@ -490,7 +490,7 @@ format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node, 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; @@ -603,8 +603,8 @@ format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart 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. */ @@ -614,12 +614,12 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node, 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); @@ -713,7 +713,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node, 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); @@ -721,7 +721,7 @@ format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node, } 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++) @@ -735,7 +735,7 @@ format_part_sprinter_entry (const void *ctx, sprinter_t *sp, 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; } @@ -898,8 +898,8 @@ show_messages (void *ctx, 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; @@ -1081,35 +1081,33 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) 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); @@ -1120,7 +1118,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) /* 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; @@ -1140,26 +1138,21 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) } } 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, @@ -1229,9 +1222,9 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) } } - 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); @@ -1241,7 +1234,7 @@ notmuch_show_command (notmuch_config_t *config, int argc, char *argv[]) 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); diff --git a/notmuch-tag.c b/notmuch-tag.c index 9c03754d..05b1837d 100644 --- a/notmuch-tag.c +++ b/notmuch-tag.c @@ -194,10 +194,10 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[]) 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; @@ -209,11 +209,11 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[]) 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); @@ -223,7 +223,7 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[]) 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", @@ -235,6 +235,8 @@ notmuch_tag_command (notmuch_config_t *config, int argc, char *argv[]) if (batch) { if (opt_index != argc) { fprintf (stderr, "Can't specify both cmdline and stdin!\n"); + if (input) + fclose (input); return EXIT_FAILURE; } } else { diff --git a/notmuch.c b/notmuch.c index 8e332ce6..539ac58c 100644 --- a/notmuch.c +++ b/notmuch.c @@ -46,14 +46,14 @@ notmuch_command (notmuch_config_t *config, int argc, char *argv[]); 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 @@ -82,8 +82,8 @@ int notmuch_minimal_options (const char *subcommand_name, 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); @@ -96,6 +96,46 @@ int notmuch_minimal_options (const char *subcommand_name, 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." }, @@ -123,6 +163,8 @@ static command_t commands[] = { "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 */ @@ -369,13 +411,13 @@ notmuch_command (notmuch_config_t *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); @@ -387,7 +429,7 @@ static notmuch_bool_t try_external_command(char *argv[]) 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]); @@ -403,15 +445,15 @@ main (int argc, char *argv[]) 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 (); diff --git a/performance-test/.gitignore b/performance-test/.gitignore index f3f9be41..8a5dabf5 100644 --- a/performance-test/.gitignore +++ b/performance-test/.gitignore @@ -1,4 +1,4 @@ -tmp.*/ -log.*/ -corpus/ -notmuch.cache.*/ +/tmp.*/ +/log.*/ +/corpus/ +/notmuch.cache.*/ diff --git a/performance-test/M00-new.sh b/performance-test/M00-new.sh index a040a97e..aab36e69 100755 --- a/performance-test/M00-new.sh +++ b/performance-test/M00-new.sh @@ -2,7 +2,7 @@ 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 diff --git a/performance-test/M01-dump-restore.sh b/performance-test/M01-dump-restore.sh index 8fea9824..32ab8dc9 100755 --- a/performance-test/M01-dump-restore.sh +++ b/performance-test/M01-dump-restore.sh @@ -2,7 +2,7 @@ test_description='dump and restore' -. ./perf-test-lib.sh || exit 1 +. $(dirname "$0")/perf-test-lib.sh || exit 1 memory_start diff --git a/performance-test/M02-show.sh b/performance-test/M02-show.sh index d73035ea..2e218fd3 100755 --- a/performance-test/M02-show.sh +++ b/performance-test/M02-show.sh @@ -2,7 +2,7 @@ test_description='show' -. ./perf-test-lib.sh || exit 1 +. $(dirname "$0")/perf-test-lib.sh || exit 1 memory_start diff --git a/performance-test/M03-search.sh b/performance-test/M03-search.sh index 8d026eee..343f5c7c 100755 --- a/performance-test/M03-search.sh +++ b/performance-test/M03-search.sh @@ -2,7 +2,7 @@ test_description='search' -. ./perf-test-lib.sh || exit 1 +. $(dirname "$0")/perf-test-lib.sh || exit 1 memory_start diff --git a/performance-test/M04-reply.sh b/performance-test/M04-reply.sh index 0e1ce087..3c1205db 100755 --- a/performance-test/M04-reply.sh +++ b/performance-test/M04-reply.sh @@ -2,7 +2,7 @@ test_description='search' -. ./perf-test-lib.sh || exit 1 +. $(dirname "$0")/perf-test-lib.sh || exit 1 memory_start diff --git a/performance-test/M05-reindex.sh b/performance-test/M05-reindex.sh new file mode 100755 index 00000000..17e2c824 --- /dev/null +++ b/performance-test/M05-reindex.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +test_description='reindex' + +. $(dirname "$0")/perf-test-lib.sh || exit 1 + +memory_start + +memory_run 'reindex *' "notmuch reindex '*'" + +memory_done diff --git a/performance-test/M06-insert.sh b/performance-test/M06-insert.sh new file mode 100755 index 00000000..5ae0656a --- /dev/null +++ b/performance-test/M06-insert.sh @@ -0,0 +1,16 @@ +#!/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 diff --git a/performance-test/T00-new.sh b/performance-test/T00-new.sh index b9f21158..68750129 100755 --- a/performance-test/T00-new.sh +++ b/performance-test/T00-new.sh @@ -2,7 +2,7 @@ test_description='notmuch new' -. ./perf-test-lib.sh || exit 1 +. $(dirname "$0")/perf-test-lib.sh || exit 1 uncache_database diff --git a/performance-test/T01-dump-restore.sh b/performance-test/T01-dump-restore.sh index 9cfd5cd6..12f12e66 100755 --- a/performance-test/T01-dump-restore.sh +++ b/performance-test/T01-dump-restore.sh @@ -2,7 +2,7 @@ test_description='dump and restore' -. ./perf-test-lib.sh || exit 1 +. $(dirname "$0")/perf-test-lib.sh || exit 1 time_start diff --git a/performance-test/T02-tag.sh b/performance-test/T02-tag.sh index dacb50b8..8c5dfd68 100755 --- a/performance-test/T02-tag.sh +++ b/performance-test/T02-tag.sh @@ -2,7 +2,7 @@ test_description='tagging' -. ./perf-test-lib.sh || exit 1 +. $(dirname "$0")/perf-test-lib.sh || exit 1 time_start diff --git a/performance-test/T03-reindex.sh b/performance-test/T03-reindex.sh new file mode 100755 index 00000000..d6d5c3c3 --- /dev/null +++ b/performance-test/T03-reindex.sh @@ -0,0 +1,13 @@ +#!/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 diff --git a/performance-test/download/.gitignore b/performance-test/download/.gitignore index 7b092346..5c356204 100644 --- a/performance-test/download/.gitignore +++ b/performance-test/download/.gitignore @@ -1,2 +1,2 @@ -*.tar.gz -*.tar.xz +/*.tar.gz +/*.tar.xz diff --git a/performance-test/perf-test-lib.sh b/performance-test/perf-test-lib.sh index c89d5aab..56538abd 100644 --- a/performance-test/perf-test-lib.sh +++ b/performance-test/perf-test-lib.sh @@ -1,4 +1,4 @@ -. ./version.sh || exit 1 +. $(dirname "$0")/version.sh || exit 1 corpus_size=large @@ -25,12 +25,16 @@ do 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 diff --git a/sprinter-json.c b/sprinter-json.c index 0a077907..c6ec8577 100644 --- a/sprinter-json.c +++ b/sprinter-json.c @@ -13,14 +13,14 @@ struct sprinter_json { /* 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; }; @@ -37,12 +37,12 @@ json_begin_value (struct sprinter *sp) 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; @@ -58,7 +58,7 @@ json_begin_aggregate (struct sprinter *sp, char open, char close) fputc (open, spj->stream); state->parent = spj->state; - state->first = TRUE; + state->first = true; state->close = close; spj->state = state; } @@ -132,7 +132,7 @@ json_integer (struct sprinter *sp, int val) } 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); @@ -154,7 +154,7 @@ json_map_key (struct sprinter *sp, const char *key) json_string (sp, key); fputs (": ", spj->stream); - spj->state->first = TRUE; + spj->state->first = true; } static void @@ -167,7 +167,7 @@ json_separator (struct sprinter *sp) { struct sprinter_json *spj = (struct sprinter_json *) sp; - spj->insert_separator = TRUE; + spj->insert_separator = true; } struct sprinter * @@ -186,7 +186,7 @@ sprinter_json_create (const void *ctx, FILE *stream) .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; diff --git a/sprinter-sexp.c b/sprinter-sexp.c index 08783e11..6891ea42 100644 --- a/sprinter-sexp.c +++ b/sprinter-sexp.c @@ -33,7 +33,7 @@ struct sprinter_sexp { /* 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 { @@ -41,7 +41,7 @@ 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 @@ -55,12 +55,12 @@ sexp_begin_value (struct sprinter *sp) 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; @@ -76,7 +76,7 @@ sexp_begin_aggregate (struct sprinter *sp) fputc ('(', sps->stream); state->parent = sps->state; - state->first = TRUE; + state->first = true; sps->state = state; } @@ -169,7 +169,7 @@ sexp_integer (struct sprinter *sp, int val) } 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); @@ -202,7 +202,7 @@ sexp_separator (struct sprinter *sp) { struct sprinter_sexp *sps = (struct sprinter_sexp *) sp; - sps->insert_separator = TRUE; + sps->insert_separator = true; } struct sprinter * @@ -221,7 +221,7 @@ sprinter_sexp_create (const void *ctx, FILE *stream) .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; diff --git a/sprinter-text.c b/sprinter-text.c index 7779488f..648b54b1 100644 --- a/sprinter-text.c +++ b/sprinter-text.c @@ -21,7 +21,7 @@ struct sprinter_text { /* 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 @@ -52,7 +52,7 @@ text_integer (struct sprinter *sp, int val) } 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; @@ -128,7 +128,7 @@ sprinter_text_create (const void *ctx, FILE *stream) .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; diff --git a/sprinter.h b/sprinter.h index f859672f..9d2e9b6f 100644 --- a/sprinter.h +++ b/sprinter.h @@ -1,7 +1,7 @@ #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 @@ -34,7 +34,7 @@ typedef struct sprinter { 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 @@ -58,7 +58,7 @@ typedef struct sprinter { /* True if this is the special-cased plain text printer. */ - notmuch_bool_t is_text_printer; + bool is_text_printer; } sprinter_t; diff --git a/tag-util.c b/tag-util.c index d9fca7b8..1837b1ae 100644 --- a/tag-util.c +++ b/tag-util.c @@ -7,7 +7,7 @@ struct _tag_operation_t { const char *tag; - notmuch_bool_t remove; + bool remove; }; struct _tag_op_list_t { @@ -35,7 +35,7 @@ line_error (tag_parse_status_t status, } 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"; @@ -84,7 +84,7 @@ parse_tag_line (void *ctx, char *line, /* 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. */ @@ -168,7 +168,7 @@ parse_tag_command_line (void *ctx, int argc, char **argv, 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); @@ -215,7 +215,7 @@ makes_changes (notmuch_message_t *message, 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); @@ -239,11 +239,11 @@ makes_changes (notmuch_message_t *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; @@ -253,7 +253,7 @@ makes_changes (notmuch_message_t *message, 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; } } @@ -264,9 +264,9 @@ makes_changes (notmuch_message_t *message, * but this is OK from a correctness point of view */ if (! exists) - return TRUE; + return true; } - return FALSE; + return false; } @@ -359,7 +359,7 @@ tag_op_list_create (void *ctx) 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. @@ -387,7 +387,7 @@ tag_op_list_append (tag_op_list_t *list, * 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); diff --git a/tag-util.h b/tag-util.h index 8a4074ce..a2f0ddfa 100644 --- a/tag-util.h +++ b/tag-util.h @@ -99,7 +99,7 @@ parse_tag_command_line (void *ctx, int argc, char **argv, * 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 @@ -111,14 +111,14 @@ tag_op_list_t * 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. @@ -157,7 +157,7 @@ tag_op_list_tag (const tag_op_list_t *list, size_t i); * 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 diff --git a/test/.gitignore b/test/.gitignore index 0579feef..73fe7e24 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,11 +1,11 @@ -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.* diff --git a/test/Makefile.local b/test/Makefile.local index 0df72c92..1a0ab813 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -62,13 +62,13 @@ test-binaries: $(TEST_BINARIES) 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 diff --git a/test/README b/test/README index 8e06f442..b378c3ff 100644 --- a/test/README +++ b/test/README @@ -80,15 +80,6 @@ The following command-line options are available when running tests: As the names depend on the tests' file names, it is safe to run the tests with this option in parallel. ---root=:: - 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 diff --git a/test/T000-basic.sh b/test/T000-basic.sh index 36a7ca4c..7fbdcfa3 100755 --- a/test/T000-basic.sh +++ b/test/T000-basic.sh @@ -4,17 +4,7 @@ # 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 @@ -23,8 +13,7 @@ test_expect_success ':' 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 @@ -43,19 +32,19 @@ fi 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 diff --git a/test/T010-help-test.sh b/test/T010-help-test.sh index 0c833de2..da45d3ae 100755 --- a/test/T010-help-test.sh +++ b/test/T010-help-test.sh @@ -1,7 +1,7 @@ #!/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' diff --git a/test/T020-compact.sh b/test/T020-compact.sh index a3d7380e..58cd2ba7 100755 --- a/test/T020-compact.sh +++ b/test/T020-compact.sh @@ -1,6 +1,6 @@ #!/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' diff --git a/test/T030-config.sh b/test/T030-config.sh index 35d757f6..e91c3659 100755 --- a/test/T030-config.sh +++ b/test/T030-config.sh @@ -1,7 +1,7 @@ #!/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" diff --git a/test/T040-setup.sh b/test/T040-setup.sh index 998bd6e0..56efe1d5 100755 --- a/test/T040-setup.sh +++ b/test/T040-setup.sh @@ -1,7 +1,7 @@ #!/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) diff --git a/test/T050-new.sh b/test/T050-new.sh index ffa303ef..2035d29f 100755 --- a/test/T050-new.sh +++ b/test/T050-new.sh @@ -1,6 +1,6 @@ #!/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) @@ -309,7 +309,7 @@ cat < notmuch-new-vanish.gdb 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 diff --git a/test/T060-count.sh b/test/T060-count.sh index 4751440e..0c0bf473 100755 --- a/test/T060-count.sh +++ b/test/T060-count.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T070-insert.sh b/test/T070-insert.sh index 48f212ee..f1650e62 100755 --- a/test/T070-insert.sh +++ b/test/T070-insert.sh @@ -1,6 +1,6 @@ #!/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 @@ -132,6 +132,13 @@ output=$(notmuch search --output=files path:Drafts/new) 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" @@ -193,7 +200,7 @@ cat < index-file-$code.gdb 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 @@ -205,13 +212,13 @@ done 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' \ @@ -219,13 +226,13 @@ for code in FILE_NOT_EMAIL READ_ONLY_DATABASE UPGRADE_REQUIRED PATH_ERROR; do 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' \ diff --git a/test/T080-search.sh b/test/T080-search.sh index d2d71ca9..70f28549 100755 --- a/test/T080-search.sh +++ b/test/T080-search.sh @@ -1,6 +1,6 @@ #!/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 @@ -111,7 +111,7 @@ thread:XXX 2009-11-18 [3/3] Adrian Perez de Castro, Keith Packard, Carl Worth; 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) diff --git a/test/T090-search-output.sh b/test/T090-search-output.sh index dccefcb7..bf28d220 100755 --- a/test/T090-search-output.sh +++ b/test/T090-search-output.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T095-address.sh b/test/T095-address.sh index 5931b147..f0291d29 100755 --- a/test/T095-address.sh +++ b/test/T095-address.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T100-search-by-folder.sh b/test/T100-search-by-folder.sh index 2844ec61..a090f3d2 100755 --- a/test/T100-search-by-folder.sh +++ b/test/T100-search-by-folder.sh @@ -1,6 +1,6 @@ #!/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"' @@ -15,7 +15,7 @@ add_message '[dir]=things/bad' '[subject]="Bites, stings, sad feelings"' 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" @@ -24,7 +24,7 @@ test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; T 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) diff --git a/test/T110-search-position-overlap-bug.sh b/test/T110-search-position-overlap-bug.sh index 2a4238f9..f4d5ee14 100755 --- a/test/T110-search-position-overlap-bug.sh +++ b/test/T110-search-position-overlap-bug.sh @@ -18,7 +18,7 @@ # 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"' diff --git a/test/T120-search-insufficient-from-quoting.sh b/test/T120-search-insufficient-from-quoting.sh index 4862d826..509fec8b 100755 --- a/test/T120-search-insufficient-from-quoting.sh +++ b/test/T120-search-insufficient-from-quoting.sh @@ -1,6 +1,6 @@ #!/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 "' \ diff --git a/test/T130-search-limiting.sh b/test/T130-search-limiting.sh index c8986f4e..8a30e7ab 100755 --- a/test/T130-search-limiting.sh +++ b/test/T130-search-limiting.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T140-excludes.sh b/test/T140-excludes.sh index f91d4d7f..0cf69975 100755 --- a/test/T140-excludes.sh +++ b/test/T140-excludes.sh @@ -1,6 +1,6 @@ #!/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" diff --git a/test/T150-tagging.sh b/test/T150-tagging.sh index 0d0a3b87..6140c676 100755 --- a/test/T150-tagging.sh +++ b/test/T150-tagging.sh @@ -1,6 +1,6 @@ #!/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' diff --git a/test/T160-json.sh b/test/T160-json.sh index ac51895e..91b98e5d 100755 --- a/test/T160-json.sh +++ b/test/T160-json.sh @@ -1,6 +1,6 @@ #!/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\"" @@ -41,13 +41,13 @@ id="json-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=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 \", \"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" @@ -71,8 +71,8 @@ test_begin_subtest "Format version: too high" 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 < EXPECTED [ [ diff --git a/test/T170-sexp.sh b/test/T170-sexp.sh index 40e5e21d..c3dcf52a 100755 --- a/test/T170-sexp.sh +++ b/test/T170-sexp.sh @@ -1,6 +1,6 @@ #!/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\"" @@ -26,25 +26,24 @@ add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 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 \" :To \"Notmuch Test Suite \" :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 \" :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 diff --git a/test/T180-text.sh b/test/T180-text.sh index 3a265dbd..ad2cb1f3 100755 --- a/test/T180-text.sh +++ b/test/T180-text.sh @@ -1,6 +1,6 @@ #!/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\"" diff --git a/test/T190-multipart.sh b/test/T190-multipart.sh index 94bb0570..f73535b7 100755 --- a/test/T190-multipart.sh +++ b/test/T190-multipart.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash test_description="output of multipart message" -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 cat < embedded_message_body Content-Type: multipart/alternative; boundary="==-=-==" diff --git a/test/T200-thread-naming.sh b/test/T200-thread-naming.sh index 2167ba8e..594d301f 100755 --- a/test/T200-thread-naming.sh +++ b/test/T200-thread-naming.sh @@ -1,6 +1,6 @@ #!/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"' \ diff --git a/test/T205-author-naming.sh b/test/T205-author-naming.sh index 69d8dc50..68b85ced 100755 --- a/test/T205-author-naming.sh +++ b/test/T205-author-naming.sh @@ -1,6 +1,6 @@ #!/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"' \ diff --git a/test/T210-raw.sh b/test/T210-raw.sh index 832a4ad3..99fdef72 100755 --- a/test/T210-raw.sh +++ b/test/T210-raw.sh @@ -1,7 +1,7 @@ #!/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 diff --git a/test/T220-reply.sh b/test/T220-reply.sh index 4fb67ffb..ebe710f9 100755 --- a/test/T220-reply.sh +++ b/test/T220-reply.sh @@ -1,6 +1,6 @@ #!/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 "' \ diff --git a/test/T230-reply-to-sender.sh b/test/T230-reply-to-sender.sh index 608334dc..134a1063 100755 --- a/test/T230-reply-to-sender.sh +++ b/test/T230-reply-to-sender.sh @@ -1,6 +1,6 @@ #!/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 "' \ diff --git a/test/T240-dump-restore.sh b/test/T240-dump-restore.sh index 75fb0b40..0870ff92 100755 --- a/test/T240-dump-restore.sh +++ b/test/T240-dump-restore.sh @@ -1,6 +1,6 @@ #!/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" diff --git a/test/T250-uuencode.sh b/test/T250-uuencode.sh index 6f45d395..251c0b40 100755 --- a/test/T250-uuencode.sh +++ b/test/T250-uuencode.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T260-thread-order.sh b/test/T260-thread-order.sh index 89f4d1be..fea61275 100755 --- a/test/T260-thread-order.sh +++ b/test/T260-thread-order.sh @@ -1,10 +1,10 @@ #!/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" diff --git a/test/T270-author-order.sh b/test/T270-author-order.sh index 9124ece6..c28ecb02 100755 --- a/test/T270-author-order.sh +++ b/test/T270-author-order.sh @@ -1,6 +1,6 @@ #!/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 "' '[date]="Sat, 01 Jan 2000 12:00:00 -0000"' diff --git a/test/T280-from-guessing.sh b/test/T280-from-guessing.sh index 7c562fb9..b8718232 100755 --- a/test/T280-from-guessing.sh +++ b/test/T280-from-guessing.sh @@ -1,6 +1,6 @@ #!/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 "' \ diff --git a/test/T290-long-id.sh b/test/T290-long-id.sh index 1fb7c037..5e3879f5 100755 --- a/test/T290-long-id.sh +++ b/test/T290-long-id.sh @@ -1,6 +1,6 @@ #!/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"' \ diff --git a/test/T300-encoding.sh b/test/T300-encoding.sh index 8d201c7e..2c656a1e 100755 --- a/test/T300-encoding.sh +++ b/test/T300-encoding.sh @@ -1,6 +1,6 @@ #!/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"' \ diff --git a/test/T310-emacs.sh b/test/T310-emacs.sh index fde11790..ba08167f 100755 --- a/test/T310-emacs.sh +++ b/test/T310-emacs.sh @@ -1,15 +1,15 @@ #!/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) @@ -401,6 +401,28 @@ Notmuch Test Suite writes: 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: /' OUTPUT +sed -i -e 's/^References: <.*>$/References: /' OUTPUT +sed -i -e '/^--text follows this line--$/q' OUTPUT +cat <EXPECTED +From: Notmuch Test Suite +To: Mikhail Gusarov +Subject: Re: [notmuch] [PATCH 1/2] Close message file after parsing message headers +In-Reply-To: +Fcc: ${MAIL_DIR}/sent +References: +--text follows this line-- +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "Reply from alternate address within emacs" add_message '[from]="Sender "' \ [to]=test_suite_other@notmuchmail.org diff --git a/test/T320-emacs-large-search-buffer.sh b/test/T320-emacs-large-search-buffer.sh index e9d5e358..f61e8a97 100755 --- a/test/T320-emacs-large-search-buffer.sh +++ b/test/T320-emacs-large-search-buffer.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T330-emacs-subject-to-filename.sh b/test/T330-emacs-subject-to-filename.sh index 517fa839..eaf7c980 100755 --- a/test/T330-emacs-subject-to-filename.sh +++ b/test/T330-emacs-subject-to-filename.sh @@ -1,7 +1,7 @@ #!/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 diff --git a/test/T340-maildir-sync.sh b/test/T340-maildir-sync.sh index 6d956635..7fece5f2 100755 --- a/test/T340-maildir-sync.sh +++ b/test/T340-maildir-sync.sh @@ -2,7 +2,7 @@ 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 @@ -28,6 +28,14 @@ add_message [subject]='"Adding message with S"' [filename]='adding-with-s-flag:2 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" @@ -49,7 +57,7 @@ test_expect_equal_json "$output" '[[[{"id": "XXXXX", "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)" @@ -153,14 +161,14 @@ cp "$MAIL_DIR/cur/duplicated-message:2," "$MAIL_DIR/cur/duplicated-message-copy: 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" @@ -181,11 +189,21 @@ test_expect_equal "$(cd $MAIL_DIR/cur/; ls non-compliant*)" "non-compliant-maild 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 diff --git a/test/T350-crypto.sh b/test/T350-crypto.sh index 7dab39a2..38615677 100755 --- a/test/T350-crypto.sh +++ b/test/T350-crypto.sh @@ -5,24 +5,7 @@ # - 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 ################################################## @@ -37,6 +20,10 @@ test_expect_success \ "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 \ @@ -231,6 +218,10 @@ test_expect_success \ "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 \ @@ -394,7 +385,7 @@ test_expect_equal_json \ 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 Subject: Re: test encrypted message 002 diff --git a/test/T355-smime.sh b/test/T355-smime.sh index 03d24581..1523f17b 100755 --- a/test/T355-smime.sh +++ b/test/T355-smime.sh @@ -1,7 +1,7 @@ #!/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 () { @@ -10,7 +10,7 @@ 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" @@ -19,7 +19,7 @@ add_gpgsm_home () 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) @@ -41,7 +41,7 @@ test_expect_success \ 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 < EXPECTED Verification successful EOF @@ -86,7 +86,7 @@ test_expect_equal_json \ 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 < EXPECTED Verification successful EOF diff --git a/test/T357-index-decryption.sh b/test/T357-index-decryption.sh new file mode 100755 index 00000000..22e716c6 --- /dev/null +++ b/test/T357-index-decryption.sh @@ -0,0 +1,170 @@ +#!/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 diff --git a/test/T360-symbol-hiding.sh b/test/T360-symbol-hiding.sh index 9c6d4e64..43921cb4 100755 --- a/test/T360-symbol-hiding.sh +++ b/test/T360-symbol-hiding.sh @@ -9,7 +9,7 @@ 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 @@ -26,8 +26,8 @@ test_begin_subtest 'checking output' 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 diff --git a/test/T370-search-folder-coherence.sh b/test/T370-search-folder-coherence.sh index d1cb45ec..0a2727e7 100755 --- a/test/T370-search-folder-coherence.sh +++ b/test/T370-search-folder-coherence.sh @@ -1,6 +1,6 @@ #!/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) @@ -32,7 +32,7 @@ test_expect_equal_file EXPECTED OUTPUT 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) diff --git a/test/T380-atomicity.sh b/test/T380-atomicity.sh index a46a2df2..45de2228 100755 --- a/test/T380-atomicity.sh +++ b/test/T380-atomicity.sh @@ -1,6 +1,6 @@ #!/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 @@ -64,7 +64,7 @@ if test_require_external_prereq gdb; then # -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 diff --git a/test/T390-python.sh b/test/T390-python.sh index a9a61145..a93a7f34 100755 --- a/test/T390-python.sh +++ b/test/T390-python.sh @@ -1,6 +1,6 @@ #!/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} @@ -56,5 +56,22 @@ grep '^[0-9a-f]' OUTPUT > INITIAL_OUTPUT 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 < EXPECTED +test_expect_equal_file EXPECTED OUTPUT test_done diff --git a/test/T395-ruby.sh b/test/T395-ruby.sh index 52f9fe0f..a0b76eb8 100755 --- a/test/T395-ruby.sh +++ b/test/T395-ruby.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T400-hooks.sh b/test/T400-hooks.sh index 7917a82f..49c690eb 100755 --- a/test/T400-hooks.sh +++ b/test/T400-hooks.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T410-argument-parsing.sh b/test/T410-argument-parsing.sh index fad134e3..71ed7e38 100755 --- a/test/T410-argument-parsing.sh +++ b/test/T410-argument-parsing.sh @@ -1,10 +1,11 @@ #!/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 < EXPECTED +boolean 1 keyword 1 flags 5 int 7 @@ -14,4 +15,26 @@ positional arg 2 pos2 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 < 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 < EXPECTED +boolean 1 +keyword 1 +flags 5 +int 7 +string foo +positional arg 1 false +EOF +test_expect_equal_file EXPECTED OUTPUT + test_done diff --git a/test/T420-emacs-test-functions.sh b/test/T420-emacs-test-functions.sh index 955c5f7f..bfc10be3 100755 --- a/test/T420-emacs-test-functions.sh +++ b/test/T420-emacs-test-functions.sh @@ -1,7 +1,7 @@ #!/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' diff --git a/test/T430-emacs-address-cleaning.sh b/test/T430-emacs-address-cleaning.sh index 664b79d2..02d3b411 100755 --- a/test/T430-emacs-address-cleaning.sh +++ b/test/T430-emacs-address-cleaning.sh @@ -1,7 +1,7 @@ #!/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)' diff --git a/test/T440-emacs-hello.sh b/test/T440-emacs-hello.sh index ac214a5b..d23c1fca 100755 --- a/test/T440-emacs-hello.sh +++ b/test/T440-emacs-hello.sh @@ -1,9 +1,9 @@ #!/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 diff --git a/test/T450-emacs-show.sh b/test/T450-emacs-show.sh index db48c7d5..d6aa5b41 100755 --- a/test/T450-emacs-show.sh +++ b/test/T450-emacs-show.sh @@ -1,9 +1,9 @@ #!/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 diff --git a/test/T455-emacs-charsets.sh b/test/T455-emacs-charsets.sh index 5d6d53a8..cb1297ca 100755 --- a/test/T455-emacs-charsets.sh +++ b/test/T455-emacs-charsets.sh @@ -1,7 +1,7 @@ #!/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' diff --git a/test/T460-emacs-tree.sh b/test/T460-emacs-tree.sh index 958ff888..0f4e4503 100755 --- a/test/T460-emacs-tree.sh +++ b/test/T460-emacs-tree.sh @@ -1,9 +1,9 @@ #!/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 diff --git a/test/T470-missing-headers.sh b/test/T470-missing-headers.sh index 32031e31..4bf5d285 100755 --- a/test/T470-missing-headers.sh +++ b/test/T470-missing-headers.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T480-hex-escaping.sh b/test/T480-hex-escaping.sh index 86cf3729..2c5bbb63 100755 --- a/test/T480-hex-escaping.sh +++ b/test/T480-hex-escaping.sh @@ -1,9 +1,9 @@ #!/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 @@ -25,7 +25,7 @@ $TEST_DIRECTORY/hex-xcode --direction=decode < EXPECTED.$test_count |\ 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 diff --git a/test/T490-parse-time-string.sh b/test/T490-parse-time-string.sh index ab90fcc5..d1c70cfa 100755 --- a/test/T490-parse-time-string.sh +++ b/test/T490-parse-time-string.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T500-search-date.sh b/test/T500-search-date.sh index f10207f8..5c5b99a0 100755 --- a/test/T500-search-date.sh +++ b/test/T500-search-date.sh @@ -1,6 +1,6 @@ #!/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 @@ -23,7 +23,7 @@ notmuch search date:18-Nov-2009_02:19:26-0800..2009-11-18_04:49:52-06:00 | notmu cat <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 diff --git a/test/T510-thread-replies.sh b/test/T510-thread-replies.sh index fa288bb1..6837ff17 100755 --- a/test/T510-thread-replies.sh +++ b/test/T510-thread-replies.sh @@ -9,7 +9,7 @@ test_description='test of proper handling of in-reply-to and references headers' # 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"' \ diff --git a/test/T520-show.sh b/test/T520-show.sh index fb232a32..16222650 100755 --- a/test/T520-show.sh +++ b/test/T520-show.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash test_description='"notmuch show"' -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 add_email_corpus diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh index f0fd1511..69ebec68 100755 --- a/test/T530-upgrade.sh +++ b/test/T530-upgrade.sh @@ -1,7 +1,7 @@ #!/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 diff --git a/test/T550-db-features.sh b/test/T550-db-features.sh index f94a660d..9d5a9e70 100755 --- a/test/T550-db-features.sh +++ b/test/T550-db-features.sh @@ -1,7 +1,7 @@ #!/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 "" diff --git a/test/T560-lib-error.sh b/test/T560-lib-error.sh index 34d15698..06a6b860 100755 --- a/test/T560-lib-error.sh +++ b/test/T560-lib-error.sh @@ -1,7 +1,7 @@ #!/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 @@ -127,7 +127,7 @@ int main (int argc, char** argv) 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); @@ -152,7 +152,7 @@ int main (int argc, char** argv) 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); diff --git a/test/T570-revision-tracking.sh b/test/T570-revision-tracking.sh index 76ad2279..a59e7c98 100755 --- a/test/T570-revision-tracking.sh +++ b/test/T570-revision-tracking.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash test_description="database revision tracking" -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 add_email_corpus diff --git a/test/T580-thread-search.sh b/test/T580-thread-search.sh index 512559a3..01aa3efd 100755 --- a/test/T580-thread-search.sh +++ b/test/T580-thread-search.sh @@ -5,7 +5,7 @@ test_description='test of searching by thread-id' -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 add_email_corpus diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh index 1b308693..46f3a76d 100755 --- a/test/T590-libconfig.sh +++ b/test/T590-libconfig.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash test_description="library config API" -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 add_email_corpus diff --git a/test/T590-thread-breakage.sh b/test/T590-thread-breakage.sh index 38abc211..aeb82cf4 100755 --- a/test/T590-thread-breakage.sh +++ b/test/T590-thread-breakage.sh @@ -19,7 +19,7 @@ test_description='thread breakage during reindexing' # 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 @@ -66,7 +66,7 @@ test_thread_count() { 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" } @@ -111,7 +111,7 @@ test_content_count banana 0 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 diff --git a/test/T600-named-queries.sh b/test/T600-named-queries.sh index 495b7699..59496c3e 100755 --- a/test/T600-named-queries.sh +++ b/test/T600-named-queries.sh @@ -1,6 +1,6 @@ #!/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" diff --git a/test/T610-message-property.sh b/test/T610-message-property.sh index ba5f55da..74b3f5a1 100755 --- a/test/T610-message-property.sh +++ b/test/T610-message-property.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash test_description="message property API" -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 add_email_corpus diff --git a/test/T620-lock.sh b/test/T620-lock.sh index f46475e8..085ffe43 100755 --- a/test/T620-lock.sh +++ b/test/T620-lock.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T630-emacs-draft.sh b/test/T630-emacs-draft.sh index cd9e33a7..d7903ce7 100755 --- a/test/T630-emacs-draft.sh +++ b/test/T630-emacs-draft.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash test_description="Emacs Draft Handling" -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 add_email_corpus diff --git a/test/T640-database-modified.sh b/test/T640-database-modified.sh index e35e35f6..274105c7 100755 --- a/test/T640-database-modified.sh +++ b/test/T640-database-modified.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T650-regexp-query.sh b/test/T650-regexp-query.sh index b7bdda11..4085340f 100755 --- a/test/T650-regexp-query.sh +++ b/test/T650-regexp-query.sh @@ -1,6 +1,6 @@ #!/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 @@ -29,7 +29,7 @@ test_expect_equal_file EXPECTED OUTPUT 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" @@ -39,7 +39,7 @@ test_expect_equal "$output" "thread:XXX 2001-01-05 [1/1] Notmuch Test Suite; T 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" diff --git a/test/T660-bad-date.sh b/test/T660-bad-date.sh index a98e11c8..f65544b9 100755 --- a/test/T660-bad-date.sh +++ b/test/T660-bad-date.sh @@ -1,6 +1,6 @@ #!/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]='"()"' diff --git a/test/T670-duplicate-mid.sh b/test/T670-duplicate-mid.sh new file mode 100755 index 00000000..c198c506 --- /dev/null +++ b/test/T670-duplicate-mid.sh @@ -0,0 +1,102 @@ +#!/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 < 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 ", + "To": "Notmuch Test Suite ", + "Date": "GENERATED_DATE" + } + }, +[]]]]' +test_expect_equal_json "$output" "$expected" + +test_begin_subtest 'Search for second subject' +cat <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 <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 < 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 ", + "To": "Notmuch Test Suite ", + "Date": "GENERATED_DATE" + } + }, +[]]]]' + +test_expect_equal_json "$output" "$expected" + +test_done diff --git a/test/T680-html-indexing.sh b/test/T680-html-indexing.sh index 74f33708..62ba8498 100755 --- a/test/T680-html-indexing.sh +++ b/test/T680-html-indexing.sh @@ -1,6 +1,6 @@ #!/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 diff --git a/test/T690-command-line-args.sh b/test/T690-command-line-args.sh index a4f4b5f5..9aa47611 100755 --- a/test/T690-command-line-args.sh +++ b/test/T690-command-line-args.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash test_description="command line arguments" -. ./test-lib.sh || exit 1 +. $(dirname "$0")/test-lib.sh || exit 1 add_message diff --git a/test/T700-reindex.sh b/test/T700-reindex.sh new file mode 100755 index 00000000..2b7bc658 --- /dev/null +++ b/test/T700-reindex.sh @@ -0,0 +1,79 @@ +#!/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 < 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 diff --git a/test/arg-test.c b/test/arg-test.c index 736686de..7aff8255 100644 --- a/test/arg-test.c +++ b/test/arg-test.c @@ -9,47 +9,61 @@ int main(int argc, char **argv){ 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); diff --git a/test/export-dirs.sh b/test/export-dirs.sh new file mode 100644 index 00000000..0578b1e5 --- /dev/null +++ b/test/export-dirs.sh @@ -0,0 +1,32 @@ +# 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 diff --git a/test/hex-xcode.c b/test/hex-xcode.c index 65d49564..33046e9a 100644 --- a/test/hex-xcode.c +++ b/test/hex-xcode.c @@ -16,7 +16,7 @@ enum direction { 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) @@ -44,17 +44,17 @@ int 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); @@ -71,7 +71,7 @@ main (int argc, char **argv) char *buffer = NULL; size_t buf_size = 0; - notmuch_bool_t read_stdin = TRUE; + bool read_stdin = true; for (; opt_index < argc; opt_index++) { @@ -82,7 +82,7 @@ main (int argc, char **argv) if (! omit_newline) putchar ('\n'); - read_stdin = FALSE; + read_stdin = false; } if (! read_stdin) diff --git a/test/notmuch-test b/test/notmuch-test index e7d3151c..ca68dd41 100755 --- a/test/notmuch-test +++ b/test/notmuch-test @@ -15,12 +15,20 @@ if [ ${BASH_VERSINFO[0]} -lt 4 ]; then 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 @@ -33,12 +41,13 @@ fi 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 @@ -46,10 +55,10 @@ trap - HUP INT TERM # 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 diff --git a/test/random-corpus.c b/test/random-corpus.c index aca694a3..9272afda 100644 --- a/test/random-corpus.c +++ b/test/random-corpus.c @@ -116,10 +116,10 @@ random_utf8_string (void *ctx, size_t char_count) /* 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)) @@ -140,7 +140,7 @@ main (int argc, char **argv) void *ctx = talloc_new (NULL); - char *config_path = NULL; + const char *config_path = NULL; notmuch_config_t *config; notmuch_database_t *notmuch; @@ -155,13 +155,13 @@ main (int argc, char **argv) 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); @@ -179,7 +179,7 @@ main (int argc, char **argv) exit (1); } - config = notmuch_config_open (ctx, config_path, FALSE); + config = notmuch_config_open (ctx, config_path, false); if (config == NULL) return 1; diff --git a/test/smtp-dummy.c b/test/smtp-dummy.c index a629927c..71992edd 100644 --- a/test/smtp-dummy.c +++ b/test/smtp-dummy.c @@ -49,211 +49,226 @@ 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 .\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 .\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] \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] \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; } diff --git a/test/test-databases/.gitignore b/test/test-databases/.gitignore index b5624b74..9452199f 100644 --- a/test/test-databases/.gitignore +++ b/test/test-databases/.gitignore @@ -1 +1 @@ -*.tar.xz +/*.tar.xz diff --git a/test/test-lib-common.sh b/test/test-lib-common.sh index ef409171..6c3571d4 100644 --- a/test/test-lib-common.sh +++ b/test/test-lib-common.sh @@ -24,39 +24,26 @@ # 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. @@ -64,13 +51,190 @@ LD_LIBRARY_PATH=${TEST_DIRECTORY%/*}/lib${LD_LIBRARY_PATH:+:$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-', where is three-digit number of the +# message. +# +# [body]=text +# +# Text to use as the body of the email message +# +# '[from]="Some User "' +# '[to]="Some User "' +# '[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 "' +# [reply-to]=some-address +# [in-reply-to]= +# [references]= +# [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 " + fi + + if [ -z "${template[to]}" ]; then + template[to]="Notmuch Test Suite " + 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 <"$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 () { @@ -131,25 +295,21 @@ then 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 @@ -165,7 +325,6 @@ mkdir -p "${HOME}" MAIL_DIR="${TMP_DIRECTORY}/mail" export NOTMUCH_CONFIG="${TMP_DIRECTORY}/notmuch-config" -mkdir -p "${test}" mkdir -p "${MAIL_DIR}" cat <"${NOTMUCH_CONFIG}" diff --git a/test/test-lib.sh b/test/test-lib.sh index 7ae96c69..42a45f15 100644 --- a/test/test-lib.sh +++ b/test/test-lib.sh @@ -26,6 +26,15 @@ fi # 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]-} @@ -93,6 +102,23 @@ unset GREP_OPTIONS # 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... @@ -134,9 +160,6 @@ do 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 @@ -276,183 +299,6 @@ export GNUPGHOME="${TEST_TMPDIR}/gnupg" 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-', where is three-digit number of the -# message. -# -# [body]=text -# -# Text to use as the body of the email message -# -# '[from]="Some User "' -# '[to]="Some User "' -# '[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 "' -# [reply-to]=some-address -# [in-reply-to]= -# [references]= -# [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 " - fi - - if [ -z "${template[to]}" ]; then - template[to]="Notmuch Test Suite " - 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 <"$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. @@ -500,8 +346,17 @@ emacs_deliver_message () # 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 @@ -520,7 +375,7 @@ emacs_fcc_message () (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. @@ -539,7 +394,7 @@ add_email_corpus () 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 @@ -627,9 +482,10 @@ test_expect_equal_json () { # 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" "$@" @@ -683,6 +539,16 @@ NOTMUCH_DUMP_TAGS () 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/' @@ -1077,8 +943,8 @@ export NOTMUCH_CONFIG=$NOTMUCH_CONFIG # --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" @@ -1093,8 +959,8 @@ test_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= @@ -1137,14 +1003,14 @@ test_python() { } 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 @@ -1200,7 +1066,7 @@ test_init_ () { } -. ./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 () { @@ -1223,7 +1089,7 @@ emacs_generate_script # 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 diff --git a/test/test-verbose b/test/test-verbose index 158be28a..8af6d9a9 100755 --- a/test/test-verbose +++ b/test/test-verbose @@ -2,7 +2,7 @@ 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 ' diff --git a/util/Makefile.local b/util/Makefile.local index 3027880b..ba03230e 100644 --- a/util/Makefile.local +++ b/util/Makefile.local @@ -5,7 +5,7 @@ extra_cflags += -I$(srcdir)/$(dir) 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) diff --git a/util/crypto.c b/util/crypto.c new file mode 100644 index 00000000..5c84282e --- /dev/null +++ b/util/crypto.c @@ -0,0 +1,140 @@ +/* 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 + */ + +#include "crypto.h" +#include +#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 diff --git a/util/crypto.h b/util/crypto.h new file mode 100644 index 00000000..1ff0297d --- /dev/null +++ b/util/crypto.h @@ -0,0 +1,31 @@ +#ifndef _CRYPTO_H +#define _CRYPTO_H + +#include +#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 diff --git a/util/error_util.c b/util/error_util.c index 778bbd52..e64162c7 100644 --- a/util/error_util.c +++ b/util/error_util.c @@ -34,6 +34,7 @@ _internal_error (const char *format, ...) fprintf (stderr, "Internal error: "); vfprintf (stderr, format, va_args); + va_end (va_args); exit (1); } diff --git a/util/gmime-extra.h b/util/gmime-extra.h index de275bc1..40bf1454 100644 --- a/util/gmime-extra.h +++ b/util/gmime-extra.h @@ -18,7 +18,6 @@ GMimeStream *g_mime_stream_stdout_new(void); #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); @@ -30,7 +29,6 @@ typedef GMimeAddressType GMimeRecipientType; #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) diff --git a/util/string-util.c b/util/string-util.c index 18125309..b0108811 100644 --- a/util/string-util.c +++ b/util/string-util.c @@ -255,3 +255,16 @@ strcase_hash (const void *ptr) 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; + } +} diff --git a/util/string-util.h b/util/string-util.h index 87917b8f..97770614 100644 --- a/util/string-util.h +++ b/util/string-util.h @@ -75,6 +75,8 @@ int strcase_equal (const void *a, const void *b); /* GLib GHashFunc compatible case insensitive hash function */ unsigned int strcase_hash (const void *ptr); +void strip_trailing (char *str, char ch); + #ifdef __cplusplus } #endif